IntroductionArticle SeriesPrerequisitesSource CodeListing files and folders in the Alfresco RepositoryGenerating module and pages and setting up routingImplementing the RepositoryParent PageImplementing the Repository List PageAfter Successful Login navigate to RepositoryAddingPaginationAdding a Toolbar with BreadcrumbNavigationAddingDrag-n-Drop File UploadEnable/Disable Upload based on User PermissionsAdding a Delete Action supporting i18nAdding aDownload File Content ActionAdding aDownloadFolder Content ActionAdding Copy and Move Files Content ActionsAdding a Manage VersionsContent ActionAdding a Create Folder ActionAdding an Upload Button to the ToolbarViewing the Details for Files and FoldersIntroduction to the Details pageImplementing theDetails pageDefining the template for the Details pageImplementing the Preview Tab for the Details pageImplementing the Properties Tabwith the ADF Card ViewAdding a Details page content action to the Repository List pageEnable Editing in the Properties tab Card ViewImplementing the Details page Properties tab with the ADF FormGenerating a new details page and setting up routingCreating and Exporting a form from APSImplementing the Form Details page templateImplementing the Form Details pagecomponent classAdding the Form Details content action to the Repository List pageAdding Search to the main toolbarIntroduction to the search functionalityGenerating a new search module and page componentsImplementing the Search Bar componentAdding the Search Bar to the main toolbarUpdate the Repository Details page component to read node-id asyncImplementing the Search Result componentWorking withMy FilesIntroduction to My FilesGeneratingthe My Files module and page componentsImplementing the My FilesParent componentImplementing the My Files List componentWorking with Alfresco Share SitesIntroduction to SitesGenerating new Sites module and page componentsImplementing the SitesParent componentImplementing the Sites List componentSummary
Introduction
In this article we will look at how you can use the Alfresco Content Services (ACS) related ADF components to build a Content Management application. This could be useful if you are about to start on an ADF trial, you need to build a PoC with ADF, or you just want to play around with it and see what's available.
This article builds on two other articles in the ADF Developer Series. The first articleΒ talks about how to generate an application with Angular CLI and prepare it for use with ADF 2.0.0 and the second articleΒ improves on the first article by adding a navigation system, menu, toolbar, and logout functionality.
We want our new content management application interface to look something like this:
Β

This application should allow the user to browse the complete Alfresco Repository. While browsing the Repository the user will be able to execute some of the more common content actions, such as Download, Details, Copy, Move, etc. There will be file Preview available so the user doesn't have to download a file to look at the content. It will also be possible to look at just My Files and also to browse and work with files via Sites. The interface will provide SearchΒ in both metadata and content.Β
Article Series
This article is part of series of articles covering ADF 2.0:
Β
Prerequisites
This articles assumes that you are starting with an ADF application that has a menu, navigation, toolbar, and logout as per thisΒ article.Β You can eitherΒ walkthrough this article first, or clone the source code as follows:
Β
Martins-Macbook-Pro:ADF mbergljung$Β git cloneΒ https://github.com/gravitonian/adf-workbench-nav20.gitΒ adf-workbench-content20
Β
This clones the starter project within a new directory calledΒ adf-workbench-content. Install all the packages for the project like this:
Β
Martins-Macbook-Pro:ADF mbergljung$ cd adf-workbench-content20
Martins-Macbook-Pro:adf-workbench-content20 mbergljung$Β npm install
Martins-Macbook-Pro:adf-workbench-content20 mbergljung$Β npmΒ dedup
TheΒ de-duplication (i.e.Β npm dedup) attempts to removes all duplicate packages byΒ moving dependencies further up the tree, where they can be more effectively shared by multiple dependent packages.Β
If you are just cloning the source code from the article, please remember that you must have Node.js 8 and Angular CLI 1.5.x already installed. If you don't, then resort to the linked article for information about how to install these tools.
Source Code
While walking through this article it is a good idea to have the source code available. You can clone the source as follows:
Β
Martins-Macbook-Pro:ADF mbergljung$Β git cloneΒ https://github.com/gravitonian/adf-workbench-content20.gitΒ adf-workbench-content20-src
Listing files and folders in the Alfresco Repository
After we have logged in we most likely want to navigate around in the Alfresco Repository and look at folders and files. This can easily be done with the ADF Document List component. We will create a page that displays the content of the Repository top folder /Company Home. The implementation will actually be in the form of a Master-Detail pattern. So we prepare for the possibility to view details for a file or a folder.
Generating module and pages and setting up routing
As usual, we can easily create a new module and components with the Angular CLI tool. Standing in the adf-workbench-content20Β directory do the following:
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$ ng g module repository --flat false --routing
Β create src/app/repository/repository-routing.module.ts (253 bytes)
Β create src/app/repository/repository.module.ts (295 bytes)
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$ cd src/app/repository/
Martins-MacBook-Pro:repository mbergljung$ ng g component repository-page
Β create src/app/repository/repository-page/repository-page.component.css (0 bytes)
Β create src/app/repository/repository-page/repository-page.component.html (34 bytes)
Β create src/app/repository/repository-page/repository-page.component.spec.ts (685 bytes)
Β create src/app/repository/repository-page/repository-page.component.ts (304 bytes)
Β update src/app/repository/repository.module.ts (405 bytes)
Β
Martins-MacBook-Pro:repository mbergljung$ ng g component repository-list-page
Β create src/app/repository/repository-list-page/repository-list-page.component.css (0 bytes)
Β create src/app/repository/repository-list-page/repository-list-page.component.html (39 bytes)
Β create src/app/repository/repository-list-page/repository-list-page.component.spec.ts (714 bytes)
Β create src/app/repository/repository-list-page/repository-list-page.component.ts (323 bytes)
Β update src/app/repository/repository.module.ts (535 bytes)
Martins-MacBook-Pro:repository mbergljung$ ng g component repository-details-page
Β create src/app/repository/repository-details-page/repository-details-page.component.css (0 bytes)
Β create src/app/repository/repository-details-page/repository-details-page.component.html (42 bytes)
Β create src/app/repository/repository-details-page/repository-details-page.component.spec.ts (735 bytes)
Β create src/app/repository/repository-details-page/repository-details-page.component.ts (335 bytes)
Β update src/app/repository/repository.module.ts (677 bytes)
This creates a repository module with routing and the following pages:
- Repository Parent page - will contain just the <router-outlet>, and it has the following child pages:
- Repository List Page - this is the Master view with the document list
- RepositoryΒ Details Page - this is the Details view for a folder or a file
Letβs configure the routing table, open up theΒ src/app/repository/repository-routing.module.tsΒ file and update it with the new route to the repository page:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RepositoryPageComponent } from './repository-page/repository-page.component';
import { RepositoryDetailsPageComponent } from './repository-details-page/repository-details-page.component';
import { RepositoryListPageComponent } from './repository-list-page/repository-list-page.component';
import { AuthGuardEcm } from '@alfresco/adf-core';
const routes: Routes = [
Β {
Β Β Β path: 'repository',
Β Β Β component: RepositoryPageComponent,
Β Β Β canActivate: [AuthGuardEcm],
Β Β Β data: {
Β Β Β Β Β title: 'Repository',
Β Β Β Β Β icon: 'folder',
Β Β Β Β Β hidden: false,
Β Β Β Β Β needEcmAuth: true,
Β Β Β Β Β isLogin: false
Β Β Β },
Β Β Β children: [
Β Β Β Β Β { path: '', component: RepositoryListPageComponent, canActivate: [AuthGuardEcm] },
Β Β Β Β Β { path: ':node-id', component: RepositoryDetailsPageComponent, canActivate: [AuthGuardEcm] }
Β Β Β ]
Β }
];
@NgModule({
Β imports: [RouterModule.forChild(routes)],
Β exports: [RouterModule]
})
export class RepositoryRoutingModule {}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWhen we use the http://localhost:4200/repositoryΒ we will hit the parent page componentΒ RepositoryPageComponent and as there is a child component RepositoryListPageComponent with empty path '' it will automatically be invoked and the document list displayed.
When one of the items in the document list is clicked, such as a file, and we select a Details content action from the 'Three Dots' menu, then the http://localhost:4200/repository/<node-id>Β URL will be invoked taking the user to theΒ RepositoryDetailsPageComponent.
If the data object properties are not familiar, then read the previous two articles mentioned in the introduction. They explain everythingΒ around these properties and the navigation system.Β
The AuthGuardEcm ADF component makes sure that the route cannot be activated if the user is not authenticated with ACS. Which leads us to make sure that the app is set up to authenticate with ACS and only that backend service. Open the src/app/app-login/app-login-page/app-login-page.component.html template file and make sure the providers property is configured as follows:
<div fxFlex="100">
<adf-login class="app-logo"
[providers]="'ECM'"
[copyrightText]="'Β© 2017 Alfresco Training.'"ββββ
...βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
For these routes to be known to the Angular Router, and then indirectly to theΒ AppMenuService, we need to import this module in theΒ AppModule.Β Open the src/app/app.module.ts file and add as follows:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AppCommonModule } from './app-common/app-common.module';
import { AppLoginRoutingModule } from './app-login/app-login-routing.module';
import { AppLoginModule } from './app-login/app-login.module';
import { AppMenuService } from './app-menu/app-menu.service';
import { RepositoryRoutingModule } from './repository/repository-routing.module';
import { RepositoryModule } from './repository/repository.module';
@NgModule({
declarations: [
Β Β AppComponent
],
imports: [
Β Β BrowserModule,
Β Β AppRoutingModule,
Β Β AppCommonModule,
Β Β AppLoginModule,
Β Β AppLoginRoutingModule,
Β Β RepositoryModule,
Β Β RepositoryRoutingModule
],
providers: [AppMenuService],
bootstrap: [AppComponent]
})
export class AppModule { }βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
Note how we also import theΒ RepositoryModuleΒ with the page components. The order that we add theΒ *RoutingModules in matter as that is the order in which they will be displayed in the side navigation.
Logging in should display the Repository link in the left navigation as follows (start the server with adf-workbench-content20 $ npm startπ

We can now start to implement the list and details pages.
Implementing the RepositoryΒ Parent Page
The parent page will just be a container for the content that is output from the child pages. Open up theΒ src/app/repository/repository-page/repository-page.component.html file and update it so it contains the Router Outlet:
<router-outlet></router-outlet>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Whenever we navigate to the Repository List page (http://localhost:4200/repository) or the Repository Details page (http://localhost:4200/repository/123) their template view will now be output in this router outlet instead of in the main app page router outlet that is contained in theΒ src/app/app.component.html template file.
Β Clicking on the Repository link in the side navigation shouldΒ now work andΒ take you to the Repository List page:

Implementing the Repository List Page
To display the contents of the Alfresco Repository we can use the ADF Document List component that is available in the @alfresco/adf-content-servicesΒ package/library (follow the link for full documentation of this component). We don't need to install this package as that has already been done in the initial articles in this series.
The ADF Document List component uses the ADF Datatable component under the covers. So whatever you know/learn about the ADF Datatable will be applicable also to the ADF Document List.
The basic usage of the ADF Document list is as follows:
<adf-document-list
Β Β #documentList
Β Β [currentFolderId]="'-my-'"
Β Β [contextMenuActions]="true"
Β Β [contentActions]="true">
</adf-document-list>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
TheΒ documentListΒ is the identifier we give this document list instance in the UI. We can choose another identifier if we like, but remember that the docs refer to this specific identifier from time to time.
TheΒ currentFolderIdΒ property is used to specify from where in the repository we want to display folders and files. The following list of constants can be used (also referred to as Data Sources):
- -root-Β : Displays content from the root of the Repository, which means from the top folder called /Company Home.
- -shared-Β : Displays shared filesΒ (This is theΒ Shared FilesΒ menu item in Alfresco Share). Same as /Company Home/Shared.
- -my-Β : Displays my files (This is theΒ My FilesΒ menu item in Alfresco Share). Same as listing /Company Home/User Homes/<user id>.
- -mysites-:Β Displays a list of Alfresco Share sites that the current user is a member of. This is the content underΒ /Company Home/Sites.
- -trashcan-Β : Displays files that the user has soft deleted. (This is theΒ User Profile | TrashcanΒ menu item inΒ Alfresco Share).
- -sharedlinks-Β : Displays a list of shared content item links.
- -sites-Β : Displays a list of Alfresco Share sites. This is the content under /Company Home/Sites.
- -favorites-Β : Displays a list of content items that the user has marked as favorites.
- -recent-Β : Displays a list of recently accessed content items.
Open up the Repository list page template file located in src/app/repository/repository-list-page/repository-list-page.component.htmlΒ and define the template as follows:
<adf-document-list
#documentList
[navigationMode]="'click'"
[currentFolderId]="'-root-'"
[contextMenuActions]="true"
[contentActions]="true">
</adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Note that we changed to useΒ -root-Β as the data source because we want to show folders and files from the topΒ /Company HomeΒ folder. TheΒ navigationModeΒ property was also set toΒ clickΒ so we donβt have to double click all the time when navigating around in the Document List.
TheΒ adf-document-listΒ tag will not be known to the application until we import theΒ AppCommonModule. Open up theΒ src/app/repository/repository.module.tsΒ and add it:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RepositoryRoutingModule } from './repository-routing.module';
import { RepositoryPageComponent } from './repository-page/repository-page.component';
import { RepositoryListPageComponent } from './repository-list-page/repository-list-page.component';
import { RepositoryDetailsPageComponent } from './repository-details-page/repository-details-page.component';
import { AppCommonModule } from '../app-common/app-common.module';
@NgModule({
Β imports: [
Β Β Β CommonModule,
Β Β Β RepositoryRoutingModule,
Β Β Β /* Common App imports (Angular Core and Material, ADF Core, Content, and Process */
Β Β Β AppCommonModule
Β ],
Β declarations: [RepositoryPageComponent, RepositoryListPageComponent, RepositoryDetailsPageComponent]
})
export class RepositoryModule {}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββTheΒ AppCommonModuleΒ will bring in all the Angular stuff, Google Material components and the ADF Content and Process components. So we should be all set to go.
You should see the following result if you login and then click on theΒ RepositoryΒ link in the side navigation:

Pretty amazing for a few lines of code!
So we got a header where we can sort ascending/descending, we got a default column layout, and we can click and navigate into a folder. We also got the context sensitive menu on each row but it is empty at the moment.
Letβs make some improvements.
After Successful Login navigate to Repository
When the user has successfully logged in it would be nice if the Repository List page was shown automatically. We can fix this with a minor update to the Login page component class. Open up theΒ src/app/app-login/app-login-page/app-login-page.component.ts file and update it as follows:
What we do here is just uncomment the router.navigate code in the onLoginSuccess method and configure it to navigate to /repository.
AddingΒ Pagination
As you might have noticed, the Document List does not have pagination built in (it used to in previous versions of ADF, but having it separate gives more freedom). The ADF component that we need to use is called <adf-pagination. Update the Repository page template file src/app/repository/repository-list-page/repository-list-page.component.htmlΒ and add the component as follows:
<adf-document-list
Β #documentList
Β [navigationMode]="'click'"
Β [currentFolderId]="'-root-'"
Β [contextMenuActions]="true"
Β [contentActions]="true">
</adf-document-list>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
<adf-pagination
Β [target]="documentList">
</adf-pagination>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
TheΒ adf-paginationΒ component has a property calledΒ targetΒ that needs to be set to point to the Document List component instance that we want to showΒ pagination for. In this caseΒ documentList. So make sure the IDs match.
The application UI now displays aΒ pagination bar at the bottom of the Document List:

The user can select how many folders and files to display per page via the "Items per page" drop down. It is also possible to navigate directly to a page via the "Page" drop down. To navigate between pages the user clicks on the left (<) and right (>) arrows.Β
It is possible to inject your own code into the pagination logic. The following events (Outputs) are available:
- (change): called for any change to the pagination bar.
- (changePageNumber): called when theΒ "Page" drop down is changed.
- (changePageSize):Β called when theΒ "Items per page" drop down is changed.
- (nextPage): called when navigating to the next page
- (prevPage):Β called when navigating to the previous page
There is also another pagination component calledΒ <adf-infinite-pagination that can be used if you want infinite scrolling.
Adding a Toolbar with BreadcrumbΒ NavigationΒ
Adding a Toolbar with Breadcrumb navigation to the Repository view would be nice as then we could navigate in the folder hierarchy more freely. Luckily there are a couple of useful ADF components for just that. Update the Repository page template file src/app/repository/repository-list-page/repository-list-page.component.htmlΒ and add the <adf-toolbar and <adf-breadcrumb components as follows:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-document-list
Β #documentList
Β [navigationMode]="'click'"
Β [currentFolderId]="'-root-'"
Β [contextMenuActions]="true"
Β [contentActions]="true">
</adf-document-list>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
<adf-pagination
Β [target]="documentList">
</adf-pagination>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
TheΒ adf-breadcrumbΒ component has a property calledΒ targetΒ that needs to be set to point to the Document List component instance that we want to show breadcrumbs for. In this caseΒ documentList. So make sure the IDs match.
TheΒ folderNodeΒ property should be set to the node that we are currently at. So we grab that directly from the document list instance withΒ documentList.folderNode.
We enclose the breadcrumb component inside anΒ adf-toolbar component so it sits nicely spaced from the document list and from the top toolbar. TheΒ document list toolbarΒ will have a different color than the top toolbar, so we set the color property to 'accent', which is part of the Google Material theming.
The application UI now displays a breadcrumb for theΒ Document List as follows:

Here I have navigated down in the folder hierarchy to the Email Templates folder. I can easily navigate back up to Company Home by clicking on that folder in the breadcrumbs toolbar.
AddingΒ Drag-n-Drop File Upload
There is currently no way of dragging and dropping files into a folder. This is usually available in the Alfresco Share folder view so users would expect this feature to be available. We can add that via the ADF Upload Drag area component, here is how you use it:
<adf-upload-drag-area
[parentId]="parent folder id where files should be uploaded"
(success)="onSuccess($event)">
Β
Β Β Β Β Β Β Component that you want to drag and drop into
</adf-upload-drag-area>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
This component is part of the @alfresco/adf-content-services package and is available without any further configuration.Β So we can add theΒ adf-upload-drag-areaΒ component directly in our template. Open up src/app/repository/repository-list-page/repository-list-page.component.htmlΒ and add it as follows:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="documentList.currentFolderId || '-root-'"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="'-root-'"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β </adf-document-list>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The most important thing here is to set theΒ parentIdΒ property correctly, it should point to where we want the content files to be uploaded. In this case they should be uploaded to the current folder of the Document List, which we can access via theΒ documentList.currentFolderIdΒ property. It is important here to give an alternative toΒ theΒ documentList.currentFolderIdΒ value when it is null, that's why we do ||Β -root-. If we don't do this, then we will get Angular change detection errors.Β
Also note that if parentId is not set properly, then files will be uploaded to theΒ /Company HomeΒ (if you have permission) folder, which might not be what you want.
Implement theΒ onDragAndDropUploadSuccessΒ method, it will be called when content has been uploaded successfully. Do this as follows in the src/app/repository/repository-list-page/repository-list-page.component.tsΒ class:
import { Component, OnInit, ViewChild } from '@angular/core';
import { DocumentListComponent } from '@alfresco/adf-content-services';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit {
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor() { }
Β ngOnInit() {Β }
Β onDragAndDropUploadSuccess($event: Event) {
Β Β Β console.log('Drag and Drop upload successful!');
Β Β Β // Refresh the page so you can see the new files
Β Β Β this.documentList.reload();
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββTheΒ onDragAndDropUploadSuccessΒ method has to do a bit of work as the folder is not automatically refreshed after an upload. So you might think that the upload did not work as you donβt see the file(s). Fix this by accessing the underlying view/template Document List component instance with the Angular ViewChildΒ decorator. Itβs then an easy task of just reloading the document list for current folder after a successful upload.
We should now see the following if we navigate into an empty folder such asΒ /Company Home/Shared:

Folders with existing content will not show this information but you can still drag and drop to them.Β If we drag and drop a file into the Shared folder we see the the log message in the Console window to the right and the file added to the folder and screen updated.
There are two moreΒ properties associated with the Drag-n-Drop component thatΒ isΒ good to know about:Β
- disabled (booleanπ can be used to disable the control (i.e. disable drag-n-drop), defaults to false.
- versioning (booleanπ controls if the uploaded files should be versioned, defaults to false.
Enable/Disable Upload based on User Permissions
So things are working nicely with the upload. However, if you create another user in ACS that does not have more than read permissions to the Content Repository, then you will get some errors in the console if you try an upload, here I'm trying an upload to the Data Dictionary that the user does not have access to:

The error spells it out βYou do not have the appropriate permissions to perform this operationβ. What we need is a way to disable the Drag-n-Drop upload functionality if the user doesnβt have permission to create stuff in a folder. We can do this with theΒ adf-node-permissionΒ and adf-nodes properties:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="documentList.currentFolderId || '-root-'"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="'-root-'"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β </adf-document-list>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β
The permission we want the user to have on current folder is βcreateβ. For more information see theΒ docs. TheΒ adf-node-permissionΒ propertyΒ can be used on any component that implements theΒ NodePermissionSubjectΒ interface, so probably good to keep in mind when working with components that interact with the Content Repository. ThisΒ functionalityΒ is part of the ADF Core Module, which we import in the RepositoryModule via the AppCommonModule.
Just specifying these properties on the component is not enough for it to work. You also need to supply the nodes that should be checked. In our case we want to supply current folder in the Document List. We can do this with theΒ adf-nodesΒ property, which gets its value via theΒ getNodesForPermissionCheckΒ method that is implemented as follows in the src/app/repository/repository-list-page/repository-list-page.component.ts class:
import { Component, OnInit, ViewChild } from '@angular/core';
import { DocumentListComponent } from '@alfresco/adf-content-services';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit {
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor() { }
Β ngOnInit() {
Β }
Β onDragAndDropUploadSuccess($event: Event) {
Β Β Β console.log('Drag and Drop upload successful!');
Β Β Β // Refresh the page so you can see the new files
Β Β Β this.documentList.reload();
Β }
Β getNodesForPermissionCheck(): MinimalNodeEntity[] {
Β Β Β if (this.documentList.folderNode) {
Β Β Β Β Β return [{entry: this.documentList.folderNode}];
Β Β Β } else {
Β Β Β Β Β return [];
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
The method is expected to return an array ofΒ MinimalNodeEntity, which is aΒ class that looks like this:
export class MinimalNodeEntity {
Β constructor(obj?: any);
Β entry?: MinimalNodeEntryEntity;
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββAnd looking atΒ MinimalNodeEntryEntityΒ we can see that this class extends theΒ MinimalNode class, which in turn extends the Node classΒ that looksΒ like this:
export class Node {
Β constructor(obj?: any);
Β id?: string;
Β name?: string;
Β nodeType?: string;
Β isFolder?: boolean;
Β isFile?: boolean;
Β isLocked?: boolean;
Β modifiedAt?: Date;
Β modifiedByUser?: UserInfo;
Β adddAt?: Date;
Β adddByUser?: UserInfo;
Β parentId?: string;
Β isLink?: boolean;
Β content?: ContentInfo;
Β aspectNames?: Array<string>;
Β properties?: any;
Β allowableOperations?: Array<string>;
Β path?: PathInfo;
Β permissions?: PermissionsInfo;
Β createdAt?: Date;
Β createdByUser?: UserInfo;
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe can easily recogniseΒ the properties of the node as similar to what we have for a node in the Alfresco Content Repository. TheΒ documentList.folderNodeΒ property is actually of theΒ MinimalNodeEntityΒ type as well, which we can see if we look at theΒ DocumentListComponentΒ source code:
export class DocumentListComponent implements OnInit, OnChanges, AfterContentInit, PaginatedComponent {
...
Β Β Β @Input()
Β Β Β folderNode: MinimalNodeEntryEntity = null;
...
Β Β Β selection = new Array<MinimalNodeEntity>();
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe can also see that when you select something in the Document list you will get the selected nodes asΒ MinimalNodeEntity.
If we test this now we will see that drag-n-drop functionality is disabled if we are logged in with a user that does not have create permissions to the folder where we are attempting the upload.
Adding a Delete Action supporting i18n
Now that we can upload new files to the Repository it would be nice to be able to delete folders and files. This can be done viaΒ content action configurationsΒ on the Document list component. The Delete action will look something like this when you click on theΒ 'Three Dots' menu for an item in the Document List:

While implementing this we will also have a look at internationalisationΒ (i18n) of UI labels, such as the βDeleteβ label for this action. We willΒ update theΒ i18n resource file (i.e. src/assets/i18n/en.json) with the labels we need for the application. See the first two articles mentioned in the introduction for more information about translations.
But as usual, we start with the template update, open up the src/app/repository/repository-list-page/repository-list-page.component.htmlΒ file and add delete actions as follows via theΒ content-actionsΒ component:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="documentList.currentFolderId || '-root-'"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="'-root-'"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onDeleteActionPermissionError($event)"
Β Β Β Β Β Β Β (success)="onDeleteActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onDeleteActionPermissionError($event)"
Β Β Β Β Β Β Β (success)="onDeleteActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
The properties and events for eachΒ content-actionΒ have the following meaning (not all are used in the above configuration):
- titleΒ - The title of the action as displayed in the UI menu, i18n resource ID in this case. Using theΒ translateΒ pipe from theΒ ngx-translate/coreΒ library. Note that the bound property format (i.e [...]) is not used here as it is easier to use normal HTML attribute format when using the translate pipe.Β
- iconΒ - aΒ Google Material DesignΒ icon id.
- targetΒ - Is this action for a βfolderβ or for a βdocumentβ (file).
- permissionΒ - the name of the permission that the user has to have on the content item to be able to delete it. In this case the user needs βdeleteβ permission.
- disableWithNoPermissionΒ - set this to true if you want this content action to be disabled in the menu (i.e. grayed out) Β if the user does not have permission to delete the content item.
- handlerΒ - this property is used when the action is available as an out-of-the-box action. Such as for example βdeleteβ, βdownloadβ, βcopyβ, and βmoveβ. For an up-to-date list of all available action handlers see theΒ backing service class. If you want to call a custom action implementation use the execute event.
- (permissionEvent)Β - thisΒ function is called if there is a problem with permissions during the delete of the content item.
- (success)Β - thisΒ function is called after the content item has been successfully deleted.
- (error)Β - thisΒ function is calledΒ when a content item could not be deleted.
- (execute) - use this instead of handler when a custom content action function should be called.
Now when we have finished configuring the actions that we need it is time to add the required event handler functions. Open up the src/app/repository/repository-list-page/repository-list-page.component.tsΒ file and add the following methods:
import { Component, OnInit, ViewChild } from '@angular/core';
import { NotificationService } from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit {
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor(private notificationService: NotificationService) { }
Β ngOnInit() {
Β }
Β onDragAndDropUploadSuccess($event: Event) {
Β Β Β console.log('Drag and Drop upload successful!');
Β Β Β // Refresh the page so you can see the new files
Β Β Β this.documentList.reload();
Β }
Β getNodesForPermissionCheck(): MinimalNodeEntity[] {
Β Β Β if (this.documentList.folderNode) {
Β Β Β Β Β return [{entry: this.documentList.folderNode}];
Β Β Β } else {
Β Β Β Β Β return [];
Β Β Β }
Β }
Β onDeleteActionPermissionError(event: any) {
Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β `You don't have the '${event.permission}' permission to do a '${event.action}' operation on the ${event.type}`,
Β Β Β Β Β 4000);
Β }
Β onDeleteActionSuccess(node) {
Β Β Β console.log('Successfully deleted a node: ' + node);
Β }
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
TheΒ onDeleteActionPermissionErrorΒ method will display a message at the bottom of the screen with a text such as βYou don't have the 'delete' permission to do a 'delete' operation on the contentβ. TheΒ onDeleteActionSuccessΒ method will write a log message such as βSuccessfully deleted a node: 4fdf9fe4-c5fe-4313-bb50-9edbada9216bβ. Note how this success method gives you back an Alfresco Node Reference for the deleted node, which might not be super useful...
Here we bring in another useful service from the ADF Core library. It is called theΒ Notification ServiceΒ and isΒ implemented on top of the Angular 2 Material Design Snackbar. ItΒ can be used to display small messages on screen.
The final thing we need to take care of are the i18n resource identifiers that we have used in our content action configurations:
<content-action
Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
<content-action
Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββUpdate the src/assets/i18n/en.jsonΒ file, it will be included in the Webpack bundle process.Β Replace whatever it contains with theΒ following content:
{
Β "DOCUMENT_LIST": {
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
Note forΒ WindowsΒ users: when copying JSON from this article it might be beneficial to have a JavaScript IDE as it will flag any characters that are not allowed in the document. Otherwise you will get problems with parsing.
You should now be able to try this out now. Note that the server need to be restarted as weΒ updated stuff under /assets, which need to be repackaged by Webpack. Login first with the admin user and make sure that you can delete folders and files. Then login with a user that does not have delete permission and make sure the action is not available then.
Adding aΒ Download File Content Action
Being able to download a file from the repository is one of the essential features that we are likely to need in most apps. Download is also one of the out-of-the-box supported ADF content actions, so it is easy to add. The action will appear in the 'Three Dots' menu as follows for files:

To add the Download content action open up theΒ src/app/repository/repository-list-page/repository-list-page.component.html file andΒ add a 'download'Β action as follows:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="documentList.currentFolderId || '-root-'"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="'-root-'"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onDeleteActionPermissionError($event)"
Β Β Β Β Β Β Β (success)="onDeleteActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [handler]="'download'">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onDeleteActionPermissionError($event)"
Β Β Β Β Β Β Β (success)="onDeleteActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe important part of the new action configuration is the handler configuration. We us the downloadΒ handler that are available out-of-the-box with ADF.Β We don't need to check permissions, because if the user can see the content item, then they have read permissions.Β
There are no event handler methods to implement. Update the i18n resource fileΒ src/assets/i18n/en.json with the englishΒ DownloadΒ translation:
{
Β "DOCUMENT_LIST": {
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DOWNLOAD": "Download",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββNow we should be ready to try out the Download action.
Adding aΒ DownloadΒ Folder Content Action
Being able to download a whole folder as a ZIP file from the repository is a useful feature that we can add to the application. Download a folder as a ZIP file is not one of the out-of-the-box supported ADF content actions, so we need to do a bit of work to get it going. The action will appear in the 'Three Dots' menu as follows for folders:

To add the Download as ZIP content action open up theΒ src/app/repository/repository-list-page/repository-list-page.component.htmlΒ file andΒ add it as follows:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="documentList.currentFolderId || '-root-'"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="'-root-'"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DOWNLOAD_AS_ZIP' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onDownloadAsZip($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onDeleteActionPermissionError($event)"
Β Β Β Β Β Β Β (success)="onDeleteActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [handler]="'download'">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onDeleteActionPermissionError($event)"
Β Β Β Β Β Β Β (success)="onDeleteActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe important part of the new action configuration is the handler configuration. We us a custom function handler that is executed by implementing the execute event on the content action.Β We don't need to check permissions, because if the user can see the folder, then they have read permissions.Β
Now, let's implement the event handlerΒ function onDownloadAsZip. Open up theΒ src/app/repository/repository-list-page/repository-list-page.component.ts file and add the handler function and a helper function called downloadZip:
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material';
import { NotificationService } from '@alfresco/adf-core';
import { DocumentListComponent, DownloadZipDialogComponent } from '@alfresco/adf-content-services';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit {
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor(private notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private dialog: MatDialog) { }
Β ngOnInit() {
Β }
Β onDragAndDropUploadSuccess($event: Event) {
Β Β Β console.log('Drag and Drop upload successful!');
Β Β Β // Refresh the page so you can see the new files
Β Β Β this.documentList.reload();
Β }
Β getNodesForPermissionCheck(): MinimalNodeEntity[] {
Β Β Β if (this.documentList.folderNode) {
Β Β Β Β Β return [{entry: this.documentList.folderNode}];
Β Β Β } else {
Β Β Β Β Β return [];
Β Β Β }
Β }
Β onDeleteActionPermissionError(event: any) {
Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β `You don't have the '${event.permission}' permission to do a '${event.action}' operation on the ${event.type}`,
Β Β Β Β Β 4000);
Β }
Β onDeleteActionSuccess(node) {
Β Β Β console.log('Successfully deleted a node: ' + node);
Β }
Β onDownloadAsZip(event: any) {
Β Β Β const node: MinimalNodeEntity = event.value;
Β Β Β this.downloadZip([node]);
Β }
Β downloadZip(selection: Array<MinimalNodeEntity>) {
Β Β Β if (selection && selection.length > 0) {
Β Β Β Β Β const nodeIds = selection.map(node => node.entry.id);
Β Β Β Β Β const dialogRef = this.dialog.open(DownloadZipDialogComponent, {
Β Β Β Β Β Β Β width: '600px',
Β Β Β Β Β Β Β data: {
Β Β Β Β Β Β Β Β Β nodeIds: nodeIds
Β Β Β Β Β Β Β }
Β Β Β Β Β });
Β Β Β Β Β dialogRef.afterClosed().subscribe(result => {
Β Β Β Β Β Β Β console.log('Download folder as ZIP result: ', result);
Β Β Β Β Β });
Β Β Β }
Β }
}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββUpdate the i18n resource fileΒ src/assets/i18n/en.jsonΒ with the englishΒ Download As Zip translation:
{
Β "DOCUMENT_LIST": {
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DOWNLOAD_AS_ZIP": "Download as ZIP",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DOWNLOAD": "Download",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββNow we should be ready to try out the new Download as Zip action. The following dialog will display when you execute this action on a folder:

The ZIP file will contain the complete folder hierarchy contained in the selected folder, including all the content files. Try it out with the Data Dictionary folder.
Adding Copy and Move Files Content ActionsΒ
Now when we are working with content actions we might as well continue and add more actions. Let's add the Copy and Move actions as well. They will appear in the 'Three Dots' menu as follows:

Adding these actions is easy as they are out-of-the-box actions. Open up theΒ src/app/repository/repository-list-page/repository-list-page.component.htmlΒ file and add copy and move actions as follows:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="documentList.currentFolderId || '-root-'"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="'-root-'"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DOWNLOAD_AS_ZIP' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onDownloadAsZip($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [handler]="'download'">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
The important part of the new action configurations is the handler configuration. We us theΒ copyΒ andΒ moveΒ handlers that are available out-of-the-box with ADF. And then we make sure that the userΒ hasΒ updateΒ permission before he or she can invoke these actions.
We have also changed the event handler functions to be generic instead and used for each content action. That is instead of having onDeleteActionSuccess, onMoveActionSuccess, onCopyActionSuccess etc. Implement the new generic action handlers as follows in src/app/repository/repository-list-page/repository-list-page.component.ts:
import { Component, OnInit, ViewChild } from '@angular/core';
import { NotificationService } from '@alfresco/adf-core';
import { DocumentListComponent, DownloadZipDialogComponent } from '@alfresco/adf-content-services';
import { MinimalNodeEntity } from 'alfresco-js-api';
import { MatDialog } from '@angular/material';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit {
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor(private notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private dialog: MatDialog) { }
Β ngOnInit() {
Β }
Β onDragAndDropUploadSuccess($event: Event) {
Β Β Β console.log('Drag and Drop upload successful!');
Β Β Β // Refresh the page so you can see the new files
Β Β Β this.documentList.reload();
Β }
Β getNodesForPermissionCheck(): MinimalNodeEntity[] {
Β Β Β if (this.documentList.folderNode) {
Β Β Β Β Β return [{entry: this.documentList.folderNode}];
Β Β Β } else {
Β Β Β Β Β return [];
Β Β Β }
Β }
Β onDownloadAsZip(event: any) {
Β Β Β const node: MinimalNodeEntity = event.value;
Β Β Β this.downloadZip([node]);
Β }
Β downloadZip(selection: Array<MinimalNodeEntity>) {
Β Β Β if (selection && selection.length > 0) {
Β Β Β Β Β const nodeIds = selection.map(node => node.entry.id);
Β Β Β Β Β const dialogRef = this.dialog.open(DownloadZipDialogComponent, {
Β Β Β Β Β Β Β width: '600px',
Β Β Β Β Β Β Β data: {
Β Β Β Β Β Β Β Β Β nodeIds: nodeIds
Β Β Β Β Β Β Β }
Β Β Β Β Β });
Β Β Β Β Β dialogRef.afterClosed().subscribe(result => {
Β Β Β Β Β Β Β console.log('Download folder as ZIP result: ', result);
Β Β Β Β Β });
Β Β Β }
Β }
Β onContentActionPermissionError(event: any) {
Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β `You don't have the '${event.permission}' permission to do a '${event.action}' operation on the ${event.type}`,
Β Β Β Β Β 4000);
Β }
Β onContentActionSuccess(nodeId) {
Β Β Β console.log('Successfully executed content action for node: ' + nodeId);
Β }
Β onContentActionError(error) {
Β Β Β console.log('There was an error executing content action: ' + error);
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββUpdate also the i18n resource fileΒ src/assets/i18n/en.json:
{
Β "DOCUMENT_LIST": {
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DOWNLOAD_AS_ZIP": "Download as ZIP",
Β Β Β Β Β Β Β "COPY": "Copy",
Β Β Β Β Β Β Β "MOVE": "Move",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DOWNLOAD": "Download",
Β Β Β Β Β Β Β "COPY": "Copy",
Β Β Β Β Β Β Β "MOVE": "Move",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββYou can now try out these new content actions. Starting with the Copy action you will see the following dialog when copying an item:

The dialog will allow both searching for a folder to copy to and navigating to a folder you want to copy to. The move action will present the same dialog.
Adding a Manage VersionsΒ Content ActionΒ
ADF now supports version management of file nodes (BTW. Alfresco Content Service does not support versioning of folders). Let's add a Manage Versions action where we can view and manage versions for a file:

This is going to have to be a custom action implementation. Open up theΒ src/app/repository/repository-list-page/repository-list-page.component.htmlΒ file and addΒ the action as follows in the File actions section:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="documentList.currentFolderId || '-root-'"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="'-root-'"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DOWNLOAD_AS_ZIP' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onDownloadAsZip($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [handler]="'download'">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MANAGE_VERSIONS' | translate}}"
Β Β Β Β Β Β Β [icon]="'storage'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β (execute)="onManageVersions($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe important part of the new action configurations is the handler configuration.Β We use the execute event and set the onManageVersions function to be called, where we will implement this functionality.Β Implement the new action handler as follows in src/app/repository/repository-list-page/repository-list-page.component.ts:
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material';
import { NotificationService } from '@alfresco/adf-core';
import { DocumentListComponent, DownloadZipDialogComponent } from '@alfresco/adf-content-services';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { VersionManagerDialogComponent } from './version-manager-dialog.component';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit {
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor(private notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private dialog: MatDialog) {Β }
Β ngOnInit() {
Β }
Β onDragAndDropUploadSuccess($event: Event) {
Β Β Β console.log('Drag and Drop upload successful!');
Β Β Β // Refresh the page so you can see the new files
Β Β Β this.documentList.reload();
Β }
Β getNodesForPermissionCheck(): MinimalNodeEntity[] {
Β Β Β if (this.documentList.folderNode) {
Β Β Β Β Β return [{entry: this.documentList.folderNode}];
Β Β Β } else {
Β Β Β Β Β return [];
Β Β Β }
Β }
Β onDownloadAsZip(event: any) {
Β Β Β const node: MinimalNodeEntity = event.value;
Β Β Β this.downloadZip([node]);
Β }
Β downloadZip(selection: Array<MinimalNodeEntity>) {
Β Β Β if (selection && selection.length > 0) {
Β Β Β Β Β const nodeIds = selection.map(node => node.entry.id);
Β Β Β Β Β const dialogRef = this.dialog.open(DownloadZipDialogComponent, {
Β Β Β Β Β Β Β width: '600px',
Β Β Β Β Β Β Β data: {
Β Β Β Β Β Β Β Β Β nodeIds: nodeIds
Β Β Β Β Β Β Β }
Β Β Β Β Β });
Β Β Β Β Β dialogRef.afterClosed().subscribe(result => {
Β Β Β Β Β Β Β console.log('Download folder as ZIP result: ', result);
Β Β Β Β Β });
Β Β Β }
Β }
Β onContentActionPermissionError(event: any) {
Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β `You don't have the '${event.permission}' permission to do a '${event.action}' operation on the ${event.type}`,
Β Β Β Β Β 4000);
Β }
Β onContentActionSuccess(nodeId) {
Β Β Β console.log('Successfully executed content action for node: ' + nodeId);
Β }
Β onContentActionError(error) {
Β Β Β console.log('There was an error executing content action: ' + error);
Β }
Β onManageVersions(event) {
Β Β Β const nodeEntry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β this.dialog.open(VersionManagerDialogComponent, {
Β Β Β Β Β data: { nodeEntry },
Β Β Β Β Β panelClass: 'adf-version-manager-dialog',
Β Β Β Β Β width: '630px'
Β Β Β });
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe will display the version information in a Angular Material Dialog component, so we inject a MatDialog in the constructor. This dialog component will need a dialog layout to display that takes aΒ MinimalNodeEntryEntity as input (i.e. the data: { nodeEntry }.Β So we define a custom dialog called VersionManagerDialogComponent. Implement it by creating a new dialog template file src/app/repository/repository-list-page/version-manager-dialog.component.html:Β
<header mat-dialog-title>Manage versions for ({{ nodeName }})</header>
<section mat-dialog-content>
Β Β Β <adf-version-manager [node]="nodeEntry"></adf-version-manager>
</section>
<footer mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
Β Β Β <button mat-button (click)="close()">Close</button>
</footer>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
This template uses the <adf-version-manager ADF component. This shows you that you can also use ADF components in dialogs. The rest of the markup is mostly related to Angular Material components.Β The corresponding component class should be implemented in theΒ src/app/repository/repository-list-page/version-manager-dialog.component.ts file as follows:Β
import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
@Component({
Β templateUrl: './version-manager-dialog.component.html',
Β encapsulation: ViewEncapsulation.None
})
export class VersionManagerDialogComponent {
Β public nodeName: string;
Β public nodeEntry: MinimalNodeEntryEntity;
Β constructor(@Inject(MAT_DIALOG_DATA) data: any,
Β Β Β Β Β Β Β Β Β Β Β Β Β private containingDialog?: MatDialogRef<VersionManagerDialogComponent>) {
Β Β Β this.nodeEntry = data.nodeEntry;
Β Β Β this.nodeName = this.nodeEntry.name;
Β }
Β close() {
Β Β Β this.containingDialog.close();
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe created theΒ VersionManagerDialogComponentΒ related files manually so we need to declare the class in the associated module. And we also need to define it as an entry component. Open up theΒ src/app/repository/repository.module.ts file and update to look like this:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RepositoryRoutingModule } from './repository-routing.module';
import { RepositoryPageComponent } from './repository-page/repository-page.component';
import { RepositoryListPageComponent } from './repository-list-page/repository-list-page.component';
import { RepositoryDetailsPageComponent } from './repository-details-page/repository-details-page.component';
import { VersionManagerDialogComponent } from './repository-list-page/version-manager-dialog.component';
import { AppCommonModule } from '../app-common/app-common.module';
@NgModule({
Β imports: [
Β Β Β CommonModule,
Β Β Β RepositoryRoutingModule,
Β Β Β /* Common App imports (Angular Core and Material, ADF Core, Content, and Process */
Β Β Β AppCommonModule
Β ],
Β declarations: [RepositoryPageComponent, RepositoryListPageComponent, RepositoryDetailsPageComponent, VersionManagerDialogComponent],
Β entryComponents: [ VersionManagerDialogComponent ],
})
export class RepositoryModule { }ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββNow, update also the i18n resource fileΒ src/assets/i18n/en.json:
{
Β "DOCUMENT_LIST": {
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DOWNLOAD_AS_ZIP": "Download as ZIP",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DOWNLOAD": "Download",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "MANAGE_VERSIONS": "Manage Versions...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββYou can now try out theΒ Manage Versions... action, a dialog such as the following should be displayed:

The dialog will show all available versions for the file and allow you to restore a previous version. It also provides a button that can be used to upload a new version of the file.
Adding a Create Folder Action
Being able to create folders is always nice. Letβs add a Create folder action to the document list. This action would need to be somewhere above the document list view as it is not associated with a specific row in the document list. We can add it to the Toolbar that currently has the breadcrumb.
We should at the end have something likeΒ follows, with the create folderΒ button on the right side of the blue toolbar:

When we click on the Create Folder button in the toolbar a dialog is displayed:

The dialog will have two input fields thatΒ representΒ the Name (i.e.Β cm:name)Β and the Description (i.e. cm:description) of the folder. The Title (cm:title) will be set to the same as Name.
We start with the template as usual, open up the
src/app/repository/repository-list-page/repository-list-page.component.html file and add the following markup:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
Β <adf-toolbar-divider></adf-toolbar-divider>
Β <button
Β Β Β mat-icon-button
Β Β Β [disabled]="!canCreateContent(documentList.folderNode)"
Β Β Β [adf-create-folder]="getDocumentListCurrentFolderId()">
Β Β Β <mat-icon>create_new_folder</mat-icon>
Β </button>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="getDocumentListCurrentFolderId()"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
...βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β
We have put the Create Folder button on the right side of the toolbar and it has anΒ mat-iconΒ with an identifier taken from the standardΒ Google Material Design icons. There is a new ADF directive called adf-create-folder that will handle everything around creating a new folder.
When we do this we also take the opportunity to add theΒ getDocumentListCurrentFolderIdΒ function that will return current folder ID of the Document List. We then use this function for both the Create Folder button, Drag-n-Drop component, and the Document List itself. This function will take care of returning a default folder ID, if no navigation has happened yet, or the current folder ID.
The only thing we have to do now is implement theΒ canCreateContentΒ and the getDocumentListCurrentFolderIdΒ Β functions in src/app/repository/repository-list-page/repository-list-page.component.ts:
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material';
import { ContentService, NotificationService } from '@alfresco/adf-core';
import { DocumentListComponent, DownloadZipDialogComponent } from '@alfresco/adf-content-services';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { VersionManagerDialogComponent } from './version-manager-dialog.component';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit, OnDestroy {
Β private onCreateFolderSubscription: Subscription;
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor(private notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private contentService: ContentService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private dialog: MatDialog) {Β }
Β ngOnInit() {
Β Β Β this.onCreateFolderSubscription =
Β Β Β Β Β this.contentService.folderCreate.subscribe(value => this.onFolderCreated(value));
Β }
Β ngOnDestroy() {
Β Β Β this.onCreateFolderSubscription.unsubscribe();
Β }
Β onDragAndDropUploadSuccess($event: Event) {
Β Β Β console.log('Drag and Drop upload successful!');
Β Β Β // Refresh the page so you can see the new files
Β Β Β this.documentList.reload();
Β }
Β getNodesForPermissionCheck(): MinimalNodeEntity[] {
Β Β Β if (this.documentList.folderNode) {
Β Β Β Β Β return [{entry: this.documentList.folderNode}];
Β Β Β } else {
Β Β Β Β Β return [];
Β Β Β }
Β }
Β onDownloadAsZip(event: any) {
Β Β Β const node: MinimalNodeEntity = event.value;
Β Β Β this.downloadZip([node]);
Β }
Β downloadZip(selection: Array<MinimalNodeEntity>) {
Β Β Β if (selection && selection.length > 0) {
Β Β Β Β Β const nodeIds = selection.map(node => node.entry.id);
Β Β Β Β Β const dialogRef = this.dialog.open(DownloadZipDialogComponent, {
Β Β Β Β Β Β Β width: '600px',
Β Β Β Β Β Β Β data: {
Β Β Β Β Β Β Β Β Β nodeIds: nodeIds
Β Β Β Β Β Β Β }
Β Β Β Β Β });
Β Β Β Β Β dialogRef.afterClosed().subscribe(result => {
Β Β Β Β Β Β Β console.log('Download folder as ZIP result: ', result);
Β Β Β Β Β });
Β Β Β }
Β }
Β onContentActionPermissionError(event: any) {
Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β `You don't have the '${event.permission}' permission to do a '${event.action}' operation on the ${event.type}`,
Β Β Β Β Β 4000);
Β }
Β onContentActionSuccess(nodeId) {
Β Β Β console.log('Successfully executed content action for node: ' + nodeId);
Β }
Β onContentActionError(error) {
Β Β Β console.log('There was an error executing content action: ' + error);
Β }
Β onManageVersions(event) {
Β Β Β const nodeEntry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β this.dialog.open(VersionManagerDialogComponent, {
Β Β Β Β Β data: {nodeEntry},
Β Β Β Β Β panelClass: 'adf-version-manager-dialog',
Β Β Β Β Β width: '630px'
Β Β Β });
Β }
Β onFolderCreated(node: MinimalNodeEntryEntity) {
Β Β Β if (node && node.parentId === this.documentList.currentFolderId) {
Β Β Β Β Β this.documentList.reload();
Β Β Β }
Β }
Β getDocumentListCurrentFolderId() {
Β Β Β return this.documentList.currentFolderId || '-root-';
Β }
Β canCreateContent(parentNode: MinimalNodeEntryEntity): boolean {
Β Β Β if (parentNode) {
Β Β Β Β Β return this.contentService.hasPermission(parentNode, 'create');
Β Β Β }
Β Β Β return false;
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThere is actually two parts to the Create Folder implementation. First the method that is called to check if we have create permission in the folder (canCreateContent). Secondly there is another method that is called when the folder has been successfully created (onFolderCreated).
The first method isΒ straightforward, itΒ executesΒ theΒ ContentService hasPermission function on the folder where we want to create a subfolder, and checks that the current user has create permission.Β Β We also use the ContentService toΒ subscribe to aΒ folderCreateΒ RxJS Subject (Observable) and it will be called whenever a folder is created. We do this in theΒ ngOnInitΒ method as follows:
private onCreateFolderSubscription: Subscription;
ngOnInit() {
Β this.onCreateFolderSubscription =
Β Β Β this.contentService.folderCreate.subscribe(value => this.onFolderCreated(value));
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ngOnDestroy() {
Β this.onCreateFolderSubscription.unsubscribe();
}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββSo whenever a folder is created we configure theΒ onFolderCreatedΒ method to be called. This method just checks that the folder that was created has a parent folder that is the current folder of the Document List, just so we donβt do stuff if a folder was created unrelated to our operation/page (remember, any component can use the Content Service). Then it reloads the document list so we can see the new folder.
You should be able to test this out now, no need to restart the server.
Adding an Upload Button to the Toolbar
We already got the drag-and-drop upload working. However, there might some browsers where this is not working. So would be good with a button in the Repository toolbar for uploading files. It would look something like this:

An upload button can easily be added to a toolbar with theΒ <adf-upload-button>Β component. Open up the src/app/repository/repository-list-page/repository-list-page.component.htmlΒ file and add it as follows:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
Β <adf-upload-button
Β Β Β [rootFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [uploadFolders]="false"
Β Β Β [multipleFiles]="true"
Β Β Β [acceptedFilesType]="'*'"
Β Β Β [versioning]="false"
Β Β Β [adf-node-permission]="'create'"
Β Β Β [adf-nodes]="getNodesForPermissionCheck()"
Β Β Β (success)="onButtonUploadSuccess($event)">
Β </adf-upload-button>
Β <adf-toolbar-divider></adf-toolbar-divider>
Β <button
Β Β Β mat-icon-button
Β Β Β [disabled]="!canCreateContent(documentList.folderNode)"
Β Β Β [adf-create-folder]="getDocumentListCurrentFolderId()">
Β Β Β <mat-icon>create_new_folder</mat-icon>
Β </button>
</adf-toolbar>
<adf-upload-drag-area
...
</adf-upload-drag-area>
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
TheΒ rootFolderIdΒ tells the upload functionality what folder the selected files should be uploaded to, which is the current folder of the document list. We also configure the upload button to only be enabled if the user hasΒ createΒ permission, this is similar to how we did it for the drag-and-drop upload.
There are a number of properties that you can set on the Upload Button:
- disabled: controls wether the button is disabled or not, defaults to false.
- uploadFolders: controls if you can upload folders or not,Β defaults toΒ false.
- multipleFiles:Β controls if you can uploadΒ multiple filesΒ or not,Β defaults toΒ false.
- versioning: controls wether the uploaded files should have versioning turned on or not,Β defaults toΒ false.
- acceptedFilesType: controls what type of files that can be uploaded, defaults to all types of files ('*')
- maxFilesSize: controls how big files that can be uploaded. Set the size in bytes. Defaults to accept any size.
- staticTitle: can be used to change the button text. Defaults to "Upload File".
- tooltip: can be used to set a tooltip text that will be displayed when you hover over the button.
- rootFolderId: See above text.
- (success): function is called after a successful upload.
- (error): function is called if something went wrong during the upload.
- (permissionEvent): function is called if trying to upload without proper permission.
After a successful upload we configure theΒ onButtonUploadSuccessΒ function to be called, which is implemented as follows in the src/app/repository/repository-list-page/repository-list-page.component.tsΒ file:
...
Β onButtonUploadSuccess($event: Event) {
Β Β Β console.log('Upload button successful!');
Β Β Β this.documentList.reload();
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe function just makes sure the document list is reloaded so it reflects the new files that were uploaded.
Viewing the Details for Files and Folders
As it stands now you cannot view (or edit) the properties (i.e. metadata) for a folder or file, this is quite standard and essential functionality. Neither can you preview a file. In this section we will fix that by implementing a Repository Details page.
Introduction to the Details page
The page will be accessible via a new content action in the Document List calledΒ Details:

Clicking theΒ DetailsΒ action takes you to a newΒ Repository DetailsΒ page that looks like this:

The first tab in theΒ DetailsΒ page shows the Preview of the document. Clicking in the second tab, which is calledΒ Properties,Β displays metadata for the file:

The last tab will display versioning information for a file node:

TheΒ DetailsΒ page also has some action buttons in the upper right corner that closes the view and takes you back to the document list, allows you to delete the content item, and provides download. If you look at a folder you will not see any theΒ PreviewΒ and VersioninsΒ tabs:

Implementing theΒ Details page
So this is the page that should display details for the content item. In Alfresco ShareΒ there are loads of thingsΒ that you can find out about an item in the details page, such as preview, metadata, workflow info, version history, comments, sharing information etc. However, we will focus on the more significant details, the properties, preview, and versions for the content item.
The preview, properties (i.e. metadata for the content item), and version info will be displayed in a tabbed view with the preview in the first tab, properties in the second one, and versions in the last tab. In fact, I could not get the preview to work in the second tab. Above the tabbed view will be a toolbar with a breadcrumb displaying the path to the content item and under it the name of the content item. The right side of the toolbar will contain three content actions: download, delete, and close.
Defining the template for the Details page
Ok, so letβs start with the template for the Details page, open up the src/app/repository/repository-details-page/repository-details-page.component.htmlΒ file and replace any existing content with the following:
<adf-toolbar [color]="'accent'" *ngIf="node">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β [folderNode]="parentFolder">
Β Β Β </adf-breadcrumb>
Β Β Β <span *ngIf="node.name">{{ node.name }}</span>
Β </adf-toolbar-title>
Β <button *ngIf="node.isFile"
Β Β Β Β Β Β Β Β Β mat-icon-button
Β Β Β Β Β Β Β Β Β matTooltip="Download this file"
Β Β Β Β Β Β Β Β Β (click)="onDownload($event)">
Β Β Β <mat-icon>file_download</mat-icon>
Β </button>
Β <button mat-icon-button
Β Β Β Β Β Β Β Β Β matTooltip="Delete this content item"
Β Β Β Β Β Β Β Β Β (click)="onDelete($event)">
Β Β Β <mat-icon>delete</mat-icon>
Β </button>
Β <adf-toolbar-divider></adf-toolbar-divider>
Β <button mat-icon-button
Β Β Β Β Β Β Β Β Β class="adf-viewer-close-button"
Β Β Β Β Β Β Β Β Β matTooltip="Close and go back to document list"
Β Β Β Β Β Β Β Β Β (click)="onGoBack($event)"
Β Β Β Β Β Β Β Β Β aria-label="Close">
Β Β Β <mat-icon>close</mat-icon>
Β </button>
</adf-toolbar>
<mat-tab-group *ngIf="node">
Β <!-- Currently the Preview has to be the first tab -->
Β <mat-tab label="Preview" *ngIf="node.isFile">
Β Β Β <div class="adf-not-overlay-viewer">
Β Β Β Β Β <adf-viewer
Β Β Β Β Β Β Β [showViewer]="true"
Β Β Β Β Β Β Β [overlayMode]="false"
Β Β Β Β Β Β Β [showToolbar]="false"
Β Β Β Β Β Β Β [fileNodeId]="node.id">
Β Β Β Β Β </adf-viewer>
Β Β Β </div>
Β </mat-tab>
Β <mat-tab label="Properties (metadata)">
Β Β Β <mat-card class="adf-card-container">
Β Β Β Β Β <mat-card-content>
Β Β Β Β Β Β Β <adf-card-view
Β Β Β Β Β Β Β Β Β [properties]="properties"
Β Β Β Β Β Β Β Β Β [editable]="true">
Β Β Β Β Β Β Β </adf-card-view>
Β Β Β Β Β Β Β <button
Β Β Β Β Β Β Β Β Β mat-icon-button
Β Β Β Β Β Β Β Β Β mat-raised-button
Β Β Β Β Β Β Β Β Β matTooltip="Save changes to properties"
Β Β Β Β Β Β Β Β Β [disabled]="isSaveDisabled()"
Β Β Β Β Β Β Β Β Β (click)="onSave($event)">
Β Β Β Β Β Β Β Β Β <mat-icon>save</mat-icon>
Β Β Β Β Β Β Β </button>
Β Β Β Β Β </mat-card-content>
Β Β Β </mat-card>
Β </mat-tab>
Β <mat-tab label="Versions" *ngIf="node.isFile">
Β Β Β <adf-version-manager [node]="node"></adf-version-manager>
Β </mat-tab>
</mat-tab-group>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe start by usingΒ the same ADF specific toolbar (adf-toolbar) that we used for the List view, which can have breadcrumb and title specifically suited for content applications. We used theΒ adf-breadcrumbΒ before when it was tightly coupled with the document list, here we just give it a folder object (parentFolder) so it can display the path to it, there is no need to link to a document list. After the breadcrumb we display the name (nodeName) of the node.
We then got three buttons for downloading, deleting, and closing the content item. Download is only available if it is a file. Note here that we use tooltips for the buttons that will be displayed when you hover over them.Β TheΒ mat-iconΒ value is as usual taken from the list of availableΒ Material Design icons.
We then use a bunch ofΒ Angular MaterialΒ components (i.e. all tags starting withΒ mat-) to build the tabbed view. These components are already imported via our src/app/app-common/app-common.module.ts module. The first tab will contain the preview of the content item, second one will display the metadata, and the last tab the version information.
Note that we use *ngIf="node" in several places to hide the markup if we are in a state of not having any node information. For example, when the template is just being loaded. If we don't do this we will get all sorts of weird errors.
Implementing the Preview Tab for the Details pageΒ
The first tab contains the content file preview (this tab is hidden for folders) and it is implemented via theΒ adf-viewerΒ component. We use the previewer without toolbar and other actions as follows:
<adf-viewer
[showViewer]="true"
[overlayMode]="false"
[showToolbar]="false"
[fileNodeId]="nodeId">
</adf-viewer>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β
It is possible to configure quite a few properties for the previewer (default values in parenthesis):
- Ways of fetching content to preview, choose one of these:
- urlFile:Β specify a URL that points directly to the file.
- blobFile:Β supply a binary BLOB with the file content bytes.
- fileNodeId: an Alfresco Node Reference for the file (this is what we use).
- overlayModeΒ (falseπΒ should the preview take over the whole screen.
- showViewerΒ (trueπ toggles the display of the viewer.
- displayName:Β name of the file being previewed, taken from URL or from Node name.
- showToolbar Β (trueπ Should the viewer have its own toolbar (we don't want this as we already got a toolbar).
- allowGoBack Β (trueπ show the back button in the toolbar.
- (goBackπΒ function that will be called when the user clicks the Go Back button in the toolbar.
- allowDownloadΒ Β (trueπ show the download button in the toolbar.
- (download): function is called when the content item isΒ downloaded.
- allowPrintΒ Β (falseπ show the print button in the toolbar.
- (print): function is called when the content item is printed.
- allowShare Β (falseπ show the share button in the toolbar.
- (share): function is called when the content item is shared
- allowSidebar (falseπΒ how theΒ sidebar button in the toolbar.
- showSidebar (falseπΒ Should the viewer have its ownΒ sidebar with metadata and version information (we don't want this as we have a separate tab for metadata).
- sidebarPosition (rightπ on which side of the viewer should the sidebar be displayed? Can also be set to 'left'.
- sidebarTemplate (nullπ the sidebar view.
- (showViewerChangeπ function is called when the viewer is closed.
- (extensionChangeπ function is called with the file MimeType or file extension.
The viewer is configured to be displayed inline ([overlayMode]="false") and this only works if we apply a style to the enclosingΒ divΒ that sets the height. We do this via theΒ adf-not-overlay-viewerΒ class. Add this class definition to the src/app/repository/repository-details-page/repository-details-page.component.scssΒ style file as follows:
.adf-not-overlay-viewer {
height:900px;
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββImplementing the Properties TabΒ with the ADF Card View
The second tab contains the content item properties and it is implemented via theΒ adf-card-viewΒ component, which is specifically designed to display a list of properties of different types, such as text, number, and date. We can use this component to display a simple view of the metadata for a content item such as file or a folder.
We will also look at how to display metadata with the ADF Form component, which gives you more layout features, and it can be designed via the Alfresco Process Services WYSIWYG UI. See next section.
We got the Details page template definedΒ soΒ letβs implement the supporting component class.
However, before we start doing that we will add a class that will keep some constants related to the Alfresco content model. CreateΒ theΒ src/app/repository/repository-content.model.ts file with the following content:
/**
* Alfresco Content Model QNames
* and other Alfresco related constants
*/
export class RepositoryContentModel {
Β static readonly TITLED_ASPECT_QNAME = 'cm:titled';
Β static readonly TITLE_PROP_QNAME = 'cm:title';
Β static readonly DESC_PROP_QNAME = 'cm:description';
Β static readonly AUTHOR_PROP_QNAME = 'cm:author';
Β static readonly NODE_BODY_PROPERTIES_KEY = 'properties';
}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββIt is a good idea to define the Alfresco content model constants in a common place and then use throughout your component classes.
Now, implement the Details page component class in the src/app/repository/repository-details-page/repository-details-page.component.tsΒ file. It is pretty much empty at the moment, add the following implementation to it:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { CardViewDateItemModel, CardViewTextItemModel, CardViewItem, ContentService, NodesApiService } from '@alfresco/adf-core';
import { RepositoryContentModel } from '../repository-content.model';
@Component({
Β selector: 'app-repository-details-page',
Β templateUrl: './repository-details-page.component.html',
Β styleUrls: ['./repository-details-page.component.scss']
})
export class RepositoryDetailsPageComponent implements OnInit {
Β node: MinimalNodeEntryEntity;
Β parentFolder: MinimalNodeEntryEntity;
Β properties: Array<CardViewItem>;
Β constructor(private router: Router,
Β Β Β Β Β Β Β Β Β Β Β Β Β private activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β Β Β Β private nodeService: NodesApiService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private contentService: ContentService) {
Β Β Β this.properties = new Array<CardViewItem>();
Β }
Β ngOnInit() {
Β Β Β const nodeId = this.activatedRoute.snapshot.params['node-id'];
Β Β Β this.nodeService.getNode(nodeId).subscribe((entry: MinimalNodeEntryEntity) => {
Β Β Β Β Β this.node = entry;
Β Β Β Β Β this.nodeService.getNode(this.node.parentId).subscribe((parentNode: MinimalNodeEntryEntity) => {
Β Β Β Β Β Β Β this.parentFolder = parentNode;
Β Β Β Β Β });
Β Β Β Β Β this.setupProps(this.node);
Β Β Β });
Β }
Β private setupProps(node: MinimalNodeEntryEntity) {
Β Β Β console.log('setupProps: ', node.id);
Β Β Β // Properties that are always available
Β Β Β const idProp = new CardViewTextItemModel({label: 'Id:', value: node.id, key: 'nodeId'});
Β Β Β const typeProp = new CardViewTextItemModel({label: 'Type:', value: node.nodeType, key: 'nodeType'});
Β Β Β const secTypeProp = new CardViewTextItemModel({
Β Β Β Β Β label: 'Secondary Types:',
Β Β Β Β Β value: node.aspectNames,
Β Β Β Β Β key: 'nodeSecTypes'
Β Β Β });
Β Β Β const creatorProp = new CardViewTextItemModel({
Β Β Β Β Β label: 'Creator:',
Β Β Β Β Β value: node.createdByUser.displayName,
Β Β Β Β Β key: 'createdBy'
Β Β Β });
Β Β Β const createdProp = new CardViewDateItemModel({
Β Β Β Β Β label: 'Created:',
Β Β Β Β Β value: node.createdAt,
Β Β Β Β Β format: 'MMM DD YYYY',
Β Β Β Β Β key: 'createdDate'
Β Β Β });
Β Β Β const modifierProp = new CardViewTextItemModel({
Β Β Β Β Β label: 'Modifier:',
Β Β Β Β Β value: node.modifiedByUser.displayName,
Β Β Β Β Β key: 'createdBy'
Β Β Β });
Β Β Β const modifiedProp = new CardViewDateItemModel({
Β Β Β Β Β label: 'Modified:',
Β Β Β Β Β value: node.modifiedAt,
Β Β Β Β Β format: 'MMM DD YYYY',
Β Β Β Β Β key: 'modifiedDate'
Β Β Β });
Β Β Β this.properties.push(idProp);
Β Β Β this.properties.push(typeProp);
Β Β Β this.properties.push(secTypeProp);
Β Β Β if (node.isFile) {
Β Β Β Β Β // Add some content file specific props
Β Β Β Β Β const sizeProp = new CardViewTextItemModel({
Β Β Β Β Β Β Β label: 'Size (bytes):',
Β Β Β Β Β Β Β value: node.content.sizeInBytes,
Β Β Β Β Β Β Β key: 'size'
Β Β Β Β Β });
Β Β Β Β Β const mimetypeProp = new CardViewTextItemModel({
Β Β Β Β Β Β Β label: 'Mimetype:',
Β Β Β Β Β Β Β value: node.content.mimeTypeName,
Β Β Β Β Β Β Β key: 'mimetype'
Β Β Β Β Β });
Β Β Β Β Β this.properties.push(sizeProp);
Β Β Β Β Β this.properties.push(mimetypeProp);
Β Β Β }
Β Β Β // Aspect properties
Β Β Β if (node.aspectNames.indexOf(RepositoryContentModel.TITLED_ASPECT_QNAME) > -1) {
Β Β Β Β Β const titleProp = new CardViewTextItemModel({label: 'Title:',
Β Β Β Β Β Β Β value: node.properties[RepositoryContentModel.TITLE_PROP_QNAME],
Β Β Β Β Β Β Β key: 'title', editable: true, default: ''});
Β Β Β Β Β const descProp = new CardViewTextItemModel({label: 'Description:',
Β Β Β Β Β Β Β value: node.properties[RepositoryContentModel.DESC_PROP_QNAME],
Β Β Β Β Β Β Β key: 'description', editable: true, default: '', multiline: true});
Β Β Β Β Β this.properties.push(titleProp);
Β Β Β Β Β this.properties.push(descProp);
Β Β Β }
Β Β Β // Author can be available if extracted during ingestion of content
Β Β Β if (node.properties && node.properties[RepositoryContentModel.AUTHOR_PROP_QNAME]) {
Β Β Β Β Β const authorProp = new CardViewTextItemModel({
Β Β Β Β Β Β Β label: 'Author:',
Β Β Β Β Β Β Β value: node.properties[RepositoryContentModel.AUTHOR_PROP_QNAME], key: 'author'
Β Β Β Β Β });
Β Β Β Β Β this.properties.push(authorProp);
Β Β Β }
Β Β Β this.properties.push(creatorProp);
Β Β Β this.properties.push(createdProp);
Β Β Β this.properties.push(modifierProp);
Β Β Β this.properties.push(modifiedProp);
Β }
Β onGoBack($event: Event) {
Β Β Β this.navigateBack2DocList();
Β }
Β onDownload($event: Event) {
Β Β Β const url = this.contentService.getContentUrl(this.node.id, true);
Β Β Β const fileName = this.node.name;
Β Β Β this.download(url, fileName);
Β }
Β onDelete($event: Event) {
Β Β Β this.nodeService.deleteNode(this.node.id).subscribe(() => {
Β Β Β Β Β this.navigateBack2DocList();
Β Β Β });
Β }
Β private navigateBack2DocList() {
Β Β Β this.router.navigate(['../'],
Β Β Β Β Β {
Β Β Β Β Β Β Β queryParams: {current_folder_id: this.parentFolder.id},
Β Β Β Β Β Β Β relativeTo: this.activatedRoute
Β Β Β Β Β });
Β }
Β private download(url: string, fileName: string) {
Β Β Β if (url && fileName) {
Β Β Β Β Β const link = document.createElement('a');
Β Β Β Β Β link.style.display = 'none';
Β Β Β Β Β link.download = fileName;
Β Β Β Β Β link.href = url;
Β Β Β Β Β document.body.appendChild(link);
Β Β Β Β Β link.click();
Β Β Β Β Β document.body.removeChild(link);
Β Β Β }
Β }
Β isSaveDisabled() {Β }
Β onSave($event: Event) {Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThis is quite a bit of code to take in at first. But letβs break it down. There are basically two things here, member variables that are mapped in the template, likeΒ nodeId, and event handler methods likeΒ onDownload()Β that are also mapped in the template.
Letβs look at theΒ member variables:
- node: TheΒ contentΒ object that should be displayed in the Details page. It is of typeΒ MinimalNodeEntryEntityΒ and it is fetchedΒ for theΒ node-idΒ that is passed in as URL path parameter toΒ the details page from the list page. Will be set in theΒ ngOnInit() method.Β The node-id is an Alfresco node referenceΒ andΒ it will look something like this: 53ef6110-ed9c-4739-a520-e7b4336229c0.
- parentFolder: The parent folder object of typeΒ MinimalNodeEntryEntityΒ that is fed to the breadcrumbs component so it can display corresponding path. Also set in theΒ ngOnInit() method.
- properties: This is an array ofΒ CardViewItemΒ objects that are fed to the ADF Card view component. These are set up in theΒ setupPropsΒ method based on aΒ MinimalNodeEntryEntityΒ object for the node we want to display properties for.
TheΒ ngOnInit()Β method is implemented as follows:
ngOnInit() {
Β Β Β const nodeId = this.activatedRoute.snapshot.params['node-id'];
Β Β Β this.nodeService.getNode(nodeId).subscribe((entry: MinimalNodeEntryEntity) => {
Β Β Β Β Β this.node = entry;
Β Β Β Β Β this.nodeService.getNode(this.node.parentId).subscribe((parentNode: MinimalNodeEntryEntity) => {
Β Β Β Β Β Β Β this.parentFolder = parentNode;
Β Β Β Β Β });
Β Β Β Β Β this.setupProps(this.node);
Β Β Β });
Β }βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe first line uses theΒ activatedRouteΒ object that has been injected via the constructor. It represents the application route that was taken to get to this page and it is based on the routing table we set up earlier. We use aΒ snapshotΒ of the route so we can get to the parameters immediately synchronously (otherwise it would be via async call). The routing table contained theΒ node-idΒ parameter for theΒ http://localhost:8080/repository/<node-id>Β route so we can extract it here like this.
We then fetch aΒ MinimalNodeEntryEntityΒ object for that node ID with the help of theΒ NodesApiServiceΒ that was injected via the constructor. As we have seen before, this type of object is useful as it gives access to loads of data for the node.Β We can see that this class extends theΒ MinimalNodeΒ class, which in turn extends theΒ NodeΒ classΒ that looksΒ like this:
export class Node {
Β constructor(obj?: any);
Β id?: string;
Β name?: string;
Β nodeType?: string;
Β isFolder?: boolean;
Β isFile?: boolean;
Β isLocked?: boolean;
Β modifiedAt?: Date;
Β modifiedByUser?: UserInfo;
Β adddAt?: Date;
Β adddByUser?: UserInfo;
Β parentId?: string;
Β isLink?: boolean;
Β content?: ContentInfo;
Β aspectNames?: Array<string>;
Β properties?: any;
Β allowableOperations?: Array<string>;
Β path?: PathInfo;
Β permissions?: PermissionsInfo;
Β createdAt?: Date;
Β createdByUser?: UserInfo;
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββLots of these properties are familiar to you if you have worked with Alfresco Repository Nodes before. We can use it to set up theΒ propertiesΒ variable that should be fed to the card view. But before we do that we also fetch the parent node for the node that is displayed.Β TheΒ nodeService.getNodeΒ method returns anΒ ObservableΒ that we canΒ subscribeΒ to. Most of the stuff in the Angular app world is asynchronous.
The last thing that we do in theΒ ngOnInit()Β method is to call theΒ setupPropsΒ method. This method sets up all the node properties that we want to display in the ADF card view. Properties are displayed in the order that they are added to the property array. There are a number ofΒ CardViewItemΒ subclasses that we can use depending on the type of property, such asΒ CardViewTextItemModelΒ andΒ CardViewDateItemModel. For more info seeΒ card view docs.Β We can see how we can extract any repository node property from any content model via theΒ node.properties['cm:title']Β notation.
If you do some JavaScript debugging you will see that aΒ MinimalNodeEntryEntityΒ object instance looks something like this:
event:
Β entry:
Β Β Β allowableOperations:(3) ["delete", "update", "updatePermissions"]
Β Β Β content:
Β Β Β Β Β encoding:"UTF-8"
Β Β Β Β Β mimeType:"application/vnd.oasis.opendocument.text"
Β Β Β Β Β mimeTypeName:"OpenDocument Text (OpenOffice 2.0)"
Β Β Β Β Β sizeInBytes:32472
Β Β Β __proto__:Object
Β Β Β createdAt:Tue Sep 12 2017 15:25:17 GMT+0100 (BST) {}
Β Β Β createdByUser:{id: "admin@app.activiti.com", displayName: "ADF User"}
Β Β Β id:"35f91e69-2ece-4b21-9f98-2f8dfbd0613f"
Β Β Β isFile:true
Β Β Β isFolder:false
Β Β Β modifiedAt:Tue Sep 12 2017 15:25:17 GMT+0100 (BST) {}
Β Β Β modifiedByUser:{id: "admin@app.activiti.com", displayName: "ADF User"}
Β Β Β name:"Installing Alfresco 4.0 on CentOS 5 using existing MySQL 5.odt"
Β Β Β nodeType:"cm:content"
Β Β Β parentId:"80ca4db6-85dd-43aa-a3d9-84c2fe321aaa"
Β Β Β path:
Β Β Β Β elements:(2) [{β¦}, {β¦}]
Β Β Β Β isComplete:true
Β Β Β Β name:"/Company Home/Guest Home"
Β Β Β __proto__:Object
Β Β Β properties:
Β Β Β Β Β cm:author:"mbergljung"
Β Β Β Β Β cm:lastThumbnailModification:(2) ["doclib:1505226318981", "pdf:1505310067960"]
Β Β Β Β Β cm:versionLabel:"1.0"
Β Β Β Β Β cm:versionType:"MAJORβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe final part of the details page component implementation contains the content action handler methods. It starts with theΒ onGoBackΒ method:
Β
onGoBack($event: Event) {
Β Β this.navigateBack2DocList();
}
private navigateBack2DocList() {
Β this.router.navigate(['../'],
Β Β {
Β Β Β Β queryParams: { current_folder_id: this.parentFolder.id },
Β Β Β Β relativeTo: this.activatedRoute
Β Β });
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWhen you click the close button in the right corner of the toolbar it will take you back to the document list, which is represented by the parent Repository List page (i.e.Β ../). When this is done you want to display the document list for the folder you came from, not the folder list for /Company Home. This is done by adding a current folder query parameter (i.e. current_folder_id) to the URL. The good thing with query parameters is that they donβt require a new route definition. We will see later how we can make use of this query parameter in the Repository list page component. Β
TheΒ next two event handler methods onDownload and onDelete are straightforward using ADF services to get to the content to be downloaded and to delete the node.Β There are also two empty functions that have to do with saving edited properties, which we will implement in a bit.
Before we can test the new details page we need to have some way of navigating to it, this is what we are going to fix in the next section.
Adding a Details page content action to the Repository List page
The Details page is now finished and we can create a content action in the List page that takes you to it. We want to implement a Details content action that looks like this:

When the user clicks the Details action for an item in the list the details page route should be activated (e.g.Β http://localhost:4200/repository/6bcdad1d-f28f-44ee-9476-b7fdcc964451). First thing we need to do is define the new content action. Open up the src/app/repository/repository-list-page/repository-list-page.component.htmlΒ template file and update it with the new Details action, putΒ it first in the list for folder actions and for document actions:
<adf-toolbar [color]="'accent'">
...
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="getDocumentListCurrentFolderId()"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'folder'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onFolderDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DOWNLOAD_AS_ZIP' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onDownloadAsZip($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'insert_drive_file'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β (execute)="onDocumentDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [handler]="'download'">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MANAGE_VERSIONS' | translate}}"
Β Β Β Β Β Β Β [icon]="'storage'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β (execute)="onManageVersions($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββNow we will update the getDocumentListCurrentFolderId implementation a bit to take into account that the user might be navigating back from the Details page to a particular folder in the Document List. We will add a new member variableΒ calledΒ currentFolderIdΒ that will keep track of where we are in the Document List.Β This way we can set the folder to display dynamically, which is useful when we navigate back from the Details page.
Update the component class in the src/app/repository/repository-list-page/repository-list-page.component.tsΒ file so it sets theΒ currentFolderIdΒ value correctly depending on if the list is displayed on the way back from the Details page or if it is the first time we display it:
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { ContentService, NotificationService } from '@alfresco/adf-core';
import { DocumentListComponent, DownloadZipDialogComponent } from '@alfresco/adf-content-services';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { VersionManagerDialogComponent } from './version-manager-dialog.component';
@Component({
Β selector: 'app-repository-list-page',
Β templateUrl: './repository-list-page.component.html',
Β styleUrls: ['./repository-list-page.component.scss']
})
export class RepositoryListPageComponent implements OnInit, OnDestroy {
Β currentFolderId = '-root-'; // By default display /Company Home
Β private onCreateFolderSubscription: Subscription;
Β @ViewChild(DocumentListComponent)
Β documentList: DocumentListComponent;
Β constructor(protected notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β Β Β Β protected contentService: ContentService,
Β Β Β Β Β Β Β Β Β Β Β Β Β protected dialog: MatDialog,
Β Β Β Β Β Β Β Β Β Β Β Β Β protected activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β Β Β Β protected router: Router) {Β }
Β ngOnInit() {
Β Β Β // Check if we should display some other folder than root
Β Β Β const currentFolderIdObservable = this.activatedRoute
Β Β Β Β Β .queryParamMap
Β Β Β Β Β .map(params => params.get('current_folder_id'));
Β Β Β currentFolderIdObservable.subscribe((id: string) => {
Β Β Β Β Β if (id) {
Β Β Β Β Β Β Β this.currentFolderId = id;
Β Β Β Β Β Β Β this.documentList.loadFolderByNodeId(this.currentFolderId);
Β Β Β Β Β }
Β Β Β });
Β Β Β this.onCreateFolderSubscription = this.contentService.folderCreate.subscribe(value => this.onFolderCreated(value));
Β }
Β ngOnDestroy() {
Β Β Β this.onCreateFolderSubscription.unsubscribe();
Β }
...
Β getDocumentListCurrentFolderId() {
Β Β Β return this.currentFolderId;
Β }
...ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββFirst we add the new class variableΒ currentFolderIdΒ and set it to default to the β-root-β store, which will be the top folder in the Alfresco Repository called /Company Home. In theΒ ngOnInit()Β method we check if we are coming from the Details page with the query parameter set to what folder that we want to display in the list.
What this means is that if we for example are located in the /Company Home/Guest Home folder, and then display the Details for a file in this folder, and then click close, we will end up in the /Company Home/Guest Home folder on the way back from the Details page, which is what we want.
TheΒ getDocumentListCurrentFolderId function is then updated to just return currentFolderId.
The component class then need to implement the two so called custom content actions. They are called custom as they donβt have out-of-the-box handler implementations, such as βdeleteβ. Instead we point to the action implementation via theΒ (execute)Β event. These new action handlers look as follows, add them to the src/app/repository/repository-list-page/repository-list-page.component.tsΒ file:
...
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
...
onFolderDetails(event: any) {
const entry: MinimalNodeEntryEntity = event.value.entry;
console.log('RepositoryListPageComponent: Navigating to details page for folder: ' + entry.name);
this.router.navigate(['/repository', entry.id]);
}
onDocumentDetails(event: any) {
const entry: MinimalNodeEntryEntity = event.value.entry;
console.log('RepositoryListPageComponent: Navigating to details page for document: ' + entry.name);
this.router.navigate(['/repository', entry.id]);
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe use the router to navigate to the Details page for the node that was selected in the list. This should be all that is needed, except the i18n resources for the extra Details actions. Things will work as is but the UI will be missing the DOCUMENT_LIST.ACTIONS.FOLDER.DETAILS and the DOCUMENT_LIST.ACTIONS.DOCUMENT.DETAILS resources. Add them in theΒ src/assets/i18n/en.jsonΒ file as follows:
{
Β "DOCUMENT_LIST": {
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DETAILS": "Details",
Β Β Β Β Β Β Β "DOWNLOAD_AS_ZIP": "Download as ZIP",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DETAILS": "Details",
Β Β Β Β Β Β Β "DOWNLOAD": "Download",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "MANAGE_VERSIONS": "Manage Versions...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe are now ready to test the new Details page implementation.
Enable Editing in the Properties tab Card View
We now got a pretty nice detail view going. However, we cannot edit any of the properties yet. This is pretty fundamental thing to be able to do. We would like to be able to edit at least the Title and the Description properties for a content item. It would look something like this when finished:

After each editable property we can see aΒ penΒ and at the bottom of the page is aΒ SaveΒ button. When clicking one of the pens an input field is displayed:

If we change one of the fields so it has a new value and then click on theΒ checkΒ mark at the end of the input field, then theΒ SaveΒ button will be active:

Clicking theΒ SaveΒ button stores the new values for the node properties to the repository and a message is displayed at the bottom of the screen.
We have already prepared the src/app/repository/repository-details-page/repository-details-page.component.htmlΒ template by adding a Save button below the properties and making the Card View editable.Β Now we have to setup each individual property that we want to be able to change asΒ editable. Open the src/app/repository/repository-details-page/repository-details-page.component.tsΒ file and set up the title and description properties as editable:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {MinimalNodeEntryEntity, NodeBody} from 'alfresco-js-api';
import {
Β CardViewDateItemModel, CardViewTextItemModel,
Β CardViewItem, ContentService, NodesApiService,
Β NotificationService, CardViewUpdateService, UpdateNotification
} from '@alfresco/adf-core';
import { RepositoryContentModel } from '../repository-content.model';
@Component({
Β selector: 'app-repository-details-page',
Β templateUrl: './repository-details-page.component.html',
Β styleUrls: ['./repository-details-page.component.scss']
})
export class RepositoryDetailsPageComponent implements OnInit {
Β node: MinimalNodeEntryEntity;
Β parentFolder: MinimalNodeEntryEntity;
Β properties: Array<CardViewItem>;
Β /* Properties to do with editing */
Β propertiesChanged = false;
Β titleProp: CardViewTextItemModel;
Β descProp: CardViewTextItemModel;
Β constructor(private router: Router,
Β Β Β Β Β Β Β Β Β Β Β Β Β private activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β Β Β Β private nodeService: NodesApiService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private contentService: ContentService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private cardViewUpdateService: CardViewUpdateService,
Β Β Β Β Β Β Β Β Β Β Β Β Β protected notificationService: NotificationService) {
Β Β Β this.properties = new Array<CardViewItem>();
Β }
Β ngOnInit() {
Β Β Β const nodeId = this.activatedRoute.snapshot.params['node-id'];
Β Β Β this.nodeService.getNode(nodeId).subscribe((entry: MinimalNodeEntryEntity) => {
Β Β Β Β Β this.node = entry;
Β Β Β Β Β this.nodeService.getNode(this.node.parentId).subscribe((parentNode: MinimalNodeEntryEntity) => {
Β Β Β Β Β Β Β this.parentFolder = parentNode;
Β Β Β Β Β });
Β Β Β Β Β this.setupProps(this.node);
Β Β Β });
Β Β Β this.cardViewUpdateService.itemUpdated$.subscribe(this.updateNodeDetails.bind(this));
Β }
Β private setupProps(node: MinimalNodeEntryEntity) {
Β Β Β console.log('setupProps: ', node.id);
Β Β Β // Properties that are always available
Β Β Β const idProp = new CardViewTextItemModel({label: 'Id:', value: node.id, key: 'nodeId'});
Β Β Β const typeProp = new CardViewTextItemModel({label: 'Type:', value: node.nodeType, key: 'nodeType'});
Β Β Β const secTypeProp = new CardViewTextItemModel({
Β Β Β Β Β label: 'Secondary Types:',
Β Β Β Β Β value: node.aspectNames,
Β Β Β Β Β key: 'nodeSecTypes'
Β Β Β });
Β Β Β const creatorProp = new CardViewTextItemModel({
Β Β Β Β Β label: 'Creator:',
Β Β Β Β Β value: node.createdByUser.displayName,
Β Β Β Β Β key: 'createdBy'
Β Β Β });
Β Β Β const createdProp = new CardViewDateItemModel({
Β Β Β Β Β label: 'Created:',
Β Β Β Β Β value: node.createdAt,
Β Β Β Β Β format: 'MMM DD YYYY',
Β Β Β Β Β key: 'createdDate'
Β Β Β });
Β Β Β const modifierProp = new CardViewTextItemModel({
Β Β Β Β Β label: 'Modifier:',
Β Β Β Β Β value: node.modifiedByUser.displayName,
Β Β Β Β Β key: 'createdBy'
Β Β Β });
Β Β Β const modifiedProp = new CardViewDateItemModel({
Β Β Β Β Β label: 'Modified:',
Β Β Β Β Β value: node.modifiedAt,
Β Β Β Β Β format: 'MMM DD YYYY',
Β Β Β Β Β key: 'modifiedDate'
Β Β Β });
Β Β Β this.properties.push(idProp);
Β Β Β this.properties.push(typeProp);
Β Β Β this.properties.push(secTypeProp);
Β Β Β if (this.node.isFile) {
Β Β Β Β Β // Add some content file specific props
Β Β Β Β Β const sizeProp = new CardViewTextItemModel({
Β Β Β Β Β Β Β label: 'Size (bytes):',
Β Β Β Β Β Β Β value: node.content.sizeInBytes,
Β Β Β Β Β Β Β key: 'size'
Β Β Β Β Β });
Β Β Β Β Β const mimetypeProp = new CardViewTextItemModel({
Β Β Β Β Β Β Β label: 'Mimetype:',
Β Β Β Β Β Β Β value: node.content.mimeTypeName,
Β Β Β Β Β Β Β key: 'mimetype'
Β Β Β Β Β });
Β Β Β Β Β this.properties.push(sizeProp);
Β Β Β Β Β this.properties.push(mimetypeProp);
Β Β Β }
Β Β Β // Aspect properties
Β Β Β if (node.aspectNames.indexOf(RepositoryContentModel.TITLED_ASPECT_QNAME) > -1) {
Β Β Β Β Β this.titleProp = new CardViewTextItemModel({label: 'Title:',
Β Β Β Β Β Β Β value: node.properties[RepositoryContentModel.TITLE_PROP_QNAME],
Β Β Β Β Β Β Β key: 'title', editable: true, default: ''});
Β Β Β Β Β this.descProp = new CardViewTextItemModel({label: 'Description:',
Β Β Β Β Β Β Β value: node.properties[RepositoryContentModel.DESC_PROP_QNAME],
Β Β Β Β Β Β Β key: 'description', editable: true, default: '', multiline: true});
Β Β Β Β Β this.properties.push(this.titleProp);
Β Β Β Β Β this.properties.push(this.descProp);
Β Β Β }
Β Β Β // Author can be available if extracted during ingestion of content
Β Β Β if (node.properties && node.properties[RepositoryContentModel.AUTHOR_PROP_QNAME]) {
Β Β Β Β Β const authorProp = new CardViewTextItemModel({
Β Β Β Β Β Β Β label: 'Author:',
Β Β Β Β Β Β Β value: node.properties[RepositoryContentModel.AUTHOR_PROP_QNAME], key: 'author'
Β Β Β Β Β });
Β Β Β Β Β this.properties.push(authorProp);
Β Β Β }
Β Β Β this.properties.push(creatorProp);
Β Β Β this.properties.push(createdProp);
Β Β Β this.properties.push(modifierProp);
Β Β Β this.properties.push(modifiedProp);
Β }
Β onGoBack($event: Event) {
Β Β Β this.navigateBack2DocList();
Β }
Β onDownload($event: Event) {
Β Β Β const url = this.contentService.getContentUrl(this.node.id, true);
Β Β Β const fileName = this.node.name;
Β Β Β this.download(url, fileName);
Β }
Β onDelete($event: Event) {
Β Β Β this.nodeService.deleteNode(this.node.id).subscribe(() => {
Β Β Β Β Β this.navigateBack2DocList();
Β Β Β });
Β }
Β private navigateBack2DocList() {
Β Β Β this.router.navigate(['../'],
Β Β Β Β Β {
Β Β Β Β Β Β Β queryParams: {current_folder_id: this.parentFolder.id},
Β Β Β Β Β Β Β relativeTo: this.activatedRoute
Β Β Β Β Β });
Β }
Β private download(url: string, fileName: string) {
Β Β Β if (url && fileName) {
Β Β Β Β Β const link = document.createElement('a');
Β Β Β Β Β link.style.display = 'none';
Β Β Β Β Β link.download = fileName;
Β Β Β Β Β link.href = url;
Β Β Β Β Β document.body.appendChild(link);
Β Β Β Β Β link.click();
Β Β Β Β Β document.body.removeChild(link);
Β Β Β }
Β }
Β private updateNodeDetails(updateNotification: UpdateNotification) {
Β Β Β const currentValue = updateNotification.target.value;
Β Β Β const newValue = updateNotification.changed[updateNotification.target.key];
Β Β Β if (currentValue !== newValue) {
Β Β Β Β Β console.log(updateNotification.target, ' = ', updateNotification.changed);
Β Β Β Β Β if (updateNotification.target.key === this.titleProp.key) {
Β Β Β Β Β Β Β this.titleProp.value = updateNotification.changed[this.titleProp.key];
Β Β Β Β Β }
Β Β Β Β Β if (updateNotification.target.key === this.descProp.key) {
Β Β Β Β Β Β Β this.descProp.value = updateNotification.changed[this.descProp.key];
Β Β Β Β Β }
Β Β Β Β Β this.propertiesChanged = true;
Β Β Β }
Β }
Β onSave($event: Event) {
Β Β Β console.log('this.titleProp.value = ', this.titleProp.value);
Β Β Β console.log('this.descProp.value = ', this.descProp.value);
Β Β Β // Set up the properties that should be updated
Β Β Β const nodeBody = <NodeBody> {};
Β Β Β nodeBody[RepositoryContentModel.NODE_BODY_PROPERTIES_KEY] = {};
Β Β Β nodeBody[RepositoryContentModel.NODE_BODY_PROPERTIES_KEY][RepositoryContentModel.TITLE_PROP_QNAME] = this.titleProp.value;
Β Β Β nodeBody[RepositoryContentModel.NODE_BODY_PROPERTIES_KEY][RepositoryContentModel.DESC_PROP_QNAME] = this.descProp.value;
Β Β Β // Make the call to Alfresco Repo and update props
Β Β Β this.nodeService.updateNode(this.node.id, nodeBody).subscribe(
Β Β Β Β Β () => {
Β Β Β Β Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β Β Β Β Β `Updated properties for '${this.node.name}' successfully`,
Β Β Β Β Β Β Β Β Β 4000);
Β Β Β Β Β }
Β Β Β );
Β Β Β this.propertiesChanged = false;
Β }
Β isSaveDisabled() {
Β Β Β return !this.propertiesChanged;
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββHere we start by adding three new properties:
- propertiesChanged: keeps track of if any of the editable properties have changed. To start with nothing have changed so we set it toΒ false.
- titleProp: now we need to have the properties we want to be editable available in the component class so we can set and get their values.
- descProp: same as fore titleProp.
We then inject theΒ CardViewUpdateServiceΒ so we can listen in on any changes happening in the Card View. So if a property value, such as Title, is changed we can be notified about it by subscribing to theΒ itemUpdatedΒ Observable in the ngOnInit function. When this happens we have theΒ updateNodeDetailsΒ function to be called.
TheΒ updateNodeDetailsΒ method will do two things if a property value has changed, first take that new property value and set it on the property object, such asΒ this.titleProp. Then set theΒ propertiesChangedΒ variable to true, which will enable the Save button.
The final thing we need to do is implement theΒ onSaveΒ handler for theΒ SaveΒ button. We can save the new properties values via theΒ this.nodeService.updateNodeΒ call. This call takes the node identifier, which is the Alfresco Node Reference as a string, and an object of typeΒ NodeBody.
TheΒ NodeBodyΒ class looks like this:
export class NodeBody {
Β constructor(obj?: any);
Β name?: string;
Β nodeType?: string;
Β aspectNames?: Array<string>;
Β properties?: {
Β Β Β [key: string]: string;
Β };
Β relativePath?: string;
Β association?: NodeBodyAssociation;
Β secondaryChildren?: Array<ChildAssociationBody>;
Β targets?: Array<AssociationBody>;
Β permissions?: PermissionsBodyUpdate;
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββSo as we can see, we can set up aΒ NodeBodyΒ structure as follows to store our new property values:
const nodeBody = <NodeBody> {
Β Β Β Β 'properties':
Β Β Β Β Β Β {
Β Β Β Β Β Β Β Β 'cm:title': this.titleProp.value,
Β Β Β Β Β Β Β Β 'cm:description': this.descProp.value
Β Β Β Β Β Β }
Β Β };ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββIf the call toΒ updateNodeΒ is successful, then we display a message with the notification service.
NoteΒ that if you plan to update the number of aspects (i.e. secondary types) for a node, then you must first take the existing aspects and add the new one to that list. You cannot just set one aspect to be added, it would replace all existing ones.
Implementing the Details page Properties tab with the ADF Form
Sometimes you would like a more complex layout for your metadata display then the ADF Card View can offer. We can then use the ADF Form component. It is very flexible and can display metadata based on an Alfresco Node Reference, a Workflow Task ID,Β in memoryΒ form etc. And the form can be designed with the APS Form Designer. Read more about theΒ <adf-form component in the official docs.
Depending on how you use the ADF Form it might require authentication with APS as it will connect and try and download form definitions. In this article we assume that the app is only authenticated with ACS. So the form will be designed in APS but downloaded and kept in the App.
We will design a new form for the node metadata and display the properties for a node as follows:

This form has been designed in APS and the JSON for it downloaded and added to the app. Notice that only two fields are updatable, the title (cm:title) and the description (cm:description). There is a SAVE button at the bottom of the form that can be used to save any updates to the title and description fields. The rest of the fields are readonly as they are controlled by the Alfresco Repository.
Next we will have a look at how a form can be created and exported from APS. But first let's just generate the module and component files that we need.
Generating a new details page and setting up routing
We will keep the Card View Details page implementation side by side with the Form Details page implementation. So let's generate a separate details page component to use for the ADF Form implementation. As usual, we can easily create aΒ page component with the Angular CLI tool. Standing in theΒ adf-workbench-content20Β directory do the following:
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$Β cd src/app/repository/
Martins-MacBook-Pro:repository mbergljung$ ng g component repository-details-form-page
Β create src/app/repository/repository-details-form-page/repository-details-form-page.component.css (0 bytes)
Β create src/app/repository/repository-details-form-page/repository-details-form-page.component.html (47 bytes)
Β create src/app/repository/repository-details-form-page/repository-details-form-page.component.spec.ts (764 bytes)
Β create src/app/repository/repository-details-form-page/repository-details-form-page.component.ts (354 bytes)
Β update src/app/repository/repository.module.ts (1460 bytes)
This creates a repository details page that we can use for the new metadata display via ADF Form.
Letβs configure the routing table with the new details page, open up theΒ src/app/repository/repository-routing.module.tsΒ file and update it with the new route to the repository details form page:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RepositoryPageComponent } from './repository-page/repository-page.component';
import { RepositoryDetailsPageComponent } from './repository-details-page/repository-details-page.component';
import { RepositoryListPageComponent } from './repository-list-page/repository-list-page.component';
import { RepositoryDetailsFormPageComponent } from './repository-details-form-page/repository-details-form-page.component';
import { AuthGuardEcm } from '@alfresco/adf-core';
const routes: Routes = [
Β {
Β Β Β path: 'repository',
Β Β Β component: RepositoryPageComponent,
Β Β Β canActivate: [AuthGuardEcm],
Β Β Β data: {
Β Β Β Β Β title: 'Repository',
Β Β Β Β Β icon: 'folder',
Β Β Β Β Β hidden: false,
Β Β Β Β Β needEcmAuth: true,
Β Β Β Β Β isLogin: false
Β Β Β },
Β Β Β children: [
Β Β Β Β Β { path: '', component: RepositoryListPageComponent, canActivate: [AuthGuardEcm] },
Β Β Β Β Β { path: ':node-id', component: RepositoryDetailsPageComponent, canActivate: [AuthGuardEcm] },
Β Β Β Β Β { path: 'form/:node-id', component: RepositoryDetailsFormPageComponent, canActivate: [AuthGuardEcm] }
Β Β Β ]
Β }
];
@NgModule({
Β imports: [RouterModule.forChild(routes)],
Β exports: [RouterModule]
})
export class RepositoryRoutingModule {}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
When one of the items in the document list is clicked, and we select a newΒ Details (form) content action,Β theΒ http://localhost:4200/repository/form/<node-id>Β URL will be invoked taking the user to the newΒ RepositoryDetailsFormPageComponent.
Creating and Exporting a form from APSΒ
Although it is technically possible to hand craft the JSON that defines the form, it is highly unlikely that you will do that as it is so much easier to do it via the form designer in Alfresco Process Services (the first article that is mentioned in the introduction shows the steps to install a trial of APS). You can access the form designer via the App Designer | Forms | Create Form:

After clicking Create new form the following form designer will be displayed:

Here youΒ can drag and drop controls into the form canvas and build up your form layout. While doing this it is important to think about the form field IDs that you are using. They will be used to fill the form with data when creating the details page component class.Β Our form will display some essential Alfresco Node metadata and look like this:
Β If we look at one of the form fields, such as Secondary Types, it has the following configuration:

The important config here is the ID, whichΒ has the valueΒ secondarytypes. When we fill in the data for the form field in ourΒ details page component classΒ we will refer to this field ID. That way the ADF Form Service can match our data withΒ a form field.
To export the JSON forΒ a form weΒ use the Export FormΒ (download) button in the upper right corner:

The source code for this article have the exported JSON for this form in theΒ src/app/repository/repository-details-form-page/alfresco-node-form.ts file. Copy it into the same place in the project you are working on (of course, if you have access to APS you can design the form from scratch and play around with the form designer to get a feel for it).
As you can see in the file, the form JSON has been wrapped inside a class and a static function (and it has been reformatted):
export class AlfrescoNodeForm {
Β static getDefinition(): any {
Β Β Β return {
Β Β Β Β Β 'id': 3011,
Β Β Β Β Β 'name': 'Alfresco Node Form',
Β Β Β Β Β 'description': 'Display basic data for an Alfresco Repository node.',
Β Β Β Β Β 'version': 1,
Β Β Β Β Β 'lastUpdatedBy': 1,
Β Β Β Β Β 'lastUpdatedByFullName': ' Administrator',
Β Β Β Β Β 'lastUpdated': '2017-10-20T12:34:42.995+0000',
Β Β Β Β Β 'stencilSetId': 0,
Β Β Β Β Β 'referenceId': null,
Β Β Β Β Β 'formDefinition': {
Β Β Β Β Β Β Β 'tabs': [],
Β Β Β Β Β Β Β 'fields': [
Β Β Β Β Β Β Β Β Β {
Β Β Β Β Β Β Β Β Β Β Β 'fieldType': 'ContainerRepresentation',
Β Β Β Β Β Β Β Β Β Β Β 'id': '1508502563892',
Β Β Β Β Β Β Β Β Β Β Β 'name': 'Label',
...ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe willΒ use this class in our details page component implementation.Β Β
You might have noticed that setting a field up as readonly via the APS Form Designer is not possible, at least I cannot find a way of doing it... So I have set up all readonlyΒ fields directly in the exported JSON file:
Β Β Β Β Β Β 'fields': {
Β Β Β Β Β Β Β Β Β Β Β Β Β '1': [
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β {
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β 'fieldType': 'FormFieldRepresentation',
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β 'id': 'secondarytypes',
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β 'name': 'Secondary Types',
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β 'type': 'text',
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β 'value': null,
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β 'required': false,
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β 'readOnly': true,ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββImplementing the Form Details page template
Let's start with the template, copy theΒ src/app/repository/repository-details-page/repository-details-page.component.html template content into theΒ src/app/repository/repository-details-form-page/repository-details-form-page.component.html template.
Then replace theΒ <adf-card-viewΒ component with anΒ <adf-form component as follows:
<adf-toolbar [color]="'accent'" *ngIf="node">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β [folderNode]="parentFolder">
Β Β Β </adf-breadcrumb>
Β Β Β <span *ngIf="node.name">{{ node.name }}</span>
Β </adf-toolbar-title>
Β <button *ngIf="node.isFile"
Β Β Β Β Β Β Β Β Β mat-icon-button
Β Β Β Β Β Β Β Β Β matTooltip="Download this file"
Β Β Β Β Β Β Β Β Β (click)="onDownload($event)">
Β Β Β <mat-icon>file_download</mat-icon>
Β </button>
Β <button mat-icon-button
Β Β Β Β Β Β Β Β Β matTooltip="Delete this content item"
Β Β Β Β Β Β Β Β Β (click)="onDelete($event)">
Β Β Β <mat-icon>delete</mat-icon>
Β </button>
Β <adf-toolbar-divider></adf-toolbar-divider>
Β <button mat-icon-button
Β Β Β Β Β Β Β Β Β class="adf-viewer-close-button"
Β Β Β Β Β Β Β Β Β matTooltip="Close and go back to document list"
Β Β Β Β Β Β Β Β Β (click)="onGoBack($event)"
Β Β Β Β Β Β Β Β Β aria-label="Close">
Β Β Β <mat-icon>close</mat-icon>
Β </button>
</adf-toolbar>
<mat-tab-group *ngIf="node">
Β <!-- Currently the Preview has to be the first tab -->
Β <mat-tab label="Preview" *ngIf="node.isFile">
Β Β Β <div class="adf-not-overlay-viewer">
Β Β Β Β Β <adf-viewer
Β Β Β Β Β Β Β [showViewer]="true"
Β Β Β Β Β Β Β [overlayMode]="false"
Β Β Β Β Β Β Β [showToolbar]="false"
Β Β Β Β Β Β Β [fileNodeId]="node.id">
Β Β Β Β Β </adf-viewer>
Β Β Β </div>
Β </mat-tab>
Β <mat-tab label="Properties (metadata)">
Β Β Β <mat-card class="adf-card-container">
Β Β Β Β Β <adf-form
Β Β Β Β Β Β Β [form]="form"
Β Β Β Β Β Β Β [showTitle]="false"
Β Β Β Β Β Β Β (formSaved)="onSave($event)">
Β Β Β Β Β </adf-form>
Β Β Β </mat-card>
Β </mat-tab>
Β <mat-tab label="Versions" *ngIf="node.isFile">
Β Β Β <adf-version-manager [node]="node"></adf-version-manager>
Β </mat-tab>
</mat-tab-group>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThere are quite a few properties and events that can be used to implementΒ a form. Here we have used only the most essential ones. The following list explains the properties that you can work withΒ (default values in parenthesis):
- There are a number of ways the form can be created, choose one of the following:
- form: this is the form model that should be used when creating the form. It is of type FormModel and containsΒ the form design (i.e. form field configuration as per exported JSON) and form field values. Typically you can get one of these objects by using theΒ formService.parseForm(formDefinitionJSON, data, readOnly) function. You can also feed this function with just the form definition JSON and get an empty form presented.
- taskId: the APS User Task ID that should be used to fetch the form definition JSON. A user task usually has an associated form (APS needs to be running so the form definition can be downloaded).
- showCompleteButtonΒ (trueπ should the complete button be visible for the task.
- disableCompleteButtonΒ (falseπ set this to true if the complete button should be disabled.
- nodeId: the ACS node identifier (i.e. node reference). The APS will be searched for a form that has a name that is the same as the content type of the node, such as 'cm:content' or 'cm:folder'.Β The form will then be populated and displayed.
- formId: the form definition JSON will be fetched from APS based on this form ID.
- formName: the form definition JSON will be fetched from APS based on this form name.
- data:Β use this toΒ supply the form field values. It is of the type FormValues. You need this if you do not supply data when invoking theΒ formService.parseForm(formDefinitionJSON)Β function.
- saveMetadata (falseπ can be used to store an APS task form as metadata in ACS. A new node will be created in ACS with all the task field data as metadata.
- path: the path in the Alfresco Repository where the new node should be stored.
- nameNode: the name of the new node.
- showTitleΒ (true): Show a form title with a refresh button in the right corner of title.
- showRefreshButtonΒ (trueπ If you don't want the refresh button in the title set this to false. The refresh button is relevant if the form data comes directly from a task instance or node instance and can be updated 'live' via the APS or ACS backing services. It is not relevant for example in our scenario in this article where form data is only updated via the app.Β
- showSaveButtonΒ (trueπ Set this to false if the Save button should be hidden. We could potentially use this to hide the button if nothing has changed.
- readOnlyΒ (falseπ should the whole form be readonly. Used when you don't pass in this infoΒ to theΒ Β formService.parseForm(formDefinitionJSON)Β function.
- showValidationIconΒ (trueπ does not seem to be used...
- showDebugButtonΒ (falseπ shows debug related information if set to true.
- fieldValidators: supply your own form field validators of type FormFieldValidator.
- (formSavedπ when the form is saved thisΒ function is called with the current FormModel. We use it in our implementation to store updated properties in the Alfresco Repository.
- (formCompletedπΒ called when the user clicked the Complete button.
- (formContentClickedπ called when content in the form is clicked.
- (formLoadedπΒ this function will be called when the data has been loaded into the form fields.
- (formDataRefreshedπ this function will be called when the data has beenΒ updated in the form fields.
- (executeOutcomeπΒ called after the form service has executed the outcome of a task form.
- (onErrorπΒ called when something went wrong in the form processing.
Implementing the Form Details pageΒ component class
Now when we got the template sorted we need to implement also the backing component class. Open upΒ the src/app/repository/repository-details-form-page/repository-details-form-page.component.tsΒ class file and update it to look like this:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MinimalNodeEntryEntity, NodeBody } from 'alfresco-js-api';
import { ContentService, FormModel, FormService, FormValues,
Β NodesApiService, NotificationService } from '@alfresco/adf-core';
import { RepositoryContentModel } from '../repository-content.model';
import { AlfrescoNodeForm } from '../repository-details-page/alfresco-node-form';
import { RepositoryFormFieldModel } from '../repository-formfield.model';
@Component({
Β selector: 'app-repository-details-form-page',
Β templateUrl: './repository-details-form-page.component.html',
Β styleUrls: ['./repository-details-form-page.component.scss']
})
export class RepositoryDetailsFormPageComponent implements OnInit {
Β node: MinimalNodeEntryEntity;
Β parentFolder: MinimalNodeEntryEntity;
Β form: FormModel;
Β originalFormData: FormValues = {};
Β constructor(private router: Router,
Β Β Β Β Β Β Β Β Β Β Β Β Β private activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β Β Β Β private nodeService: NodesApiService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private contentService: ContentService,
Β Β Β Β Β Β Β Β Β Β Β Β Β private formService: FormService,
Β Β Β Β Β Β Β Β Β Β Β Β Β protected notificationService: NotificationService) {
Β }
Β ngOnInit() {
Β Β Β const nodeId = this.activatedRoute.snapshot.params['node-id'];
Β Β Β this.nodeService.getNode(nodeId).subscribe((entry: MinimalNodeEntryEntity) => {
Β Β Β Β Β this.node = entry;
Β Β Β Β Β this.nodeService.getNode(this.node.parentId).subscribe((parentNode: MinimalNodeEntryEntity) => {
Β Β Β Β Β Β Β this.parentFolder = parentNode;
Β Β Β Β Β });
Β Β Β Β Β this.setupFormData(this.node);
Β Β Β });
Β }
Β private setupFormData(node: MinimalNodeEntryEntity) {
Β Β Β console.log('setupFormData: ', node.id);
Β Β Β // Content file specific props
Β Β Β let size = 'N/A';
Β Β Β let mimetype = 'N/A';
Β Β Β if (node.isFile) {
Β Β Β Β Β size = '' + node.content.sizeInBytes;
Β Β Β Β Β mimetype = node.content.mimeTypeName;
Β Β Β }
Β Β Β // Aspect properties
Β Β Β let title = '';
Β Β Β let desc = '';
Β Β Β if (node.aspectNames.indexOf(RepositoryContentModel.TITLED_ASPECT_QNAME) > -1) {
Β Β Β Β Β title = node.properties[RepositoryContentModel.TITLE_PROP_QNAME];
Β Β Β Β Β desc = node.properties[RepositoryContentModel.DESC_PROP_QNAME];
Β Β Β }
Β Β Β // Author can be available if extracted during ingestion of content
Β Β Β let author = '';
Β Β Β if (node.properties && node.properties[RepositoryContentModel.AUTHOR_PROP_QNAME]) {
Β Β Β Β Β author = node.properties[RepositoryContentModel.AUTHOR_PROP_QNAME];
Β Β Β }
Β Β Β this.originalFormData = {
Β Β Β Β Β 'id': node.id,
Β Β Β Β Β 'type': node.nodeType,
Β Β Β Β Β 'secondarytypes': node.aspectNames,
Β Β Β Β Β 'creator': node.createdByUser.displayName,
Β Β Β Β Β 'created': node.createdAt,
Β Β Β Β Β 'modifier': node.modifiedByUser.displayName,
Β Β Β Β Β 'modified': node.modifiedAt,
Β Β Β Β Β 'sizebytes': size,
Β Β Β Β Β 'mimetype': mimetype,
Β Β Β Β Β 'title': title,
Β Β Β Β Β 'description': desc,
Β Β Β Β Β 'author': author
Β Β Β };
Β Β Β // Read and parse the form that we will use to display the node
Β Β Β const formDefinitionJSON: any = AlfrescoNodeForm.getDefinition();
Β Β Β const readOnly = false;
Β Β Β this.form = this.formService.parseForm(formDefinitionJSON, this.originalFormData, readOnly);
Β }
Β onGoBack($event: Event) {
Β Β Β this.navigateBack2DocList();
Β }
Β onDownload($event: Event) {
Β Β Β const url = this.contentService.getContentUrl(this.node.id, true);
Β Β Β const fileName = this.node.name;
Β Β Β this.download(url, fileName);
Β }
Β onDelete($event: Event) {
Β Β Β this.nodeService.deleteNode(this.node.id).subscribe(() => {
Β Β Β Β Β this.navigateBack2DocList();
Β Β Β });
Β }
Β onSave(form: FormModel) {
Β Β Β const titleChanged = this.form.values[RepositoryFormFieldModel.TITLE_FIELD_NAME] &&
Β Β Β Β Β (this.form.values[RepositoryFormFieldModel.TITLE_FIELD_NAME] !==
Β Β Β Β Β Β Β this.originalFormData[RepositoryFormFieldModel.TITLE_FIELD_NAME]);
Β Β Β const descriptionChanged = this.form.values[RepositoryFormFieldModel.DESC_FIELD_NAME] &&
Β Β Β Β Β (this.form.values[RepositoryFormFieldModel.DESC_FIELD_NAME] !==
Β Β Β Β Β Β Β this.originalFormData[RepositoryFormFieldModel.DESC_FIELD_NAME]);
Β Β Β if (titleChanged || descriptionChanged) {
Β Β Β Β Β // We got some non-readonly metadata that has been updated
Β Β Β Β Β console.log('Updating [cm:title = ' + this.form.values[RepositoryFormFieldModel.TITLE_FIELD_NAME] + ']');
Β Β Β Β Β console.log('Updating [cm:description = ' + this.form.values[RepositoryFormFieldModel.DESC_FIELD_NAME] + ']');
Β Β Β Β Β // Set up the properties that should be updated
Β Β Β Β Β const nodeBody = <NodeBody> {};
Β Β Β Β Β nodeBody[RepositoryContentModel.NODE_BODY_PROPERTIES_KEY] = {};
Β Β Β Β Β nodeBody[RepositoryContentModel.NODE_BODY_PROPERTIES_KEY][RepositoryContentModel.TITLE_PROP_QNAME] = this.form.values['title'];
Β Β Β Β Β nodeBody[RepositoryContentModel.NODE_BODY_PROPERTIES_KEY][RepositoryContentModel.DESC_PROP_QNAME] = this.form.values['description'];
Β Β Β Β Β // Make the call to Alfresco Repo and update props
Β Β Β Β Β this.nodeService.updateNode(this.node.id, nodeBody).subscribe(
Β Β Β Β Β Β Β () => {
Β Β Β Β Β Β Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β Β Β Β Β Β Β `Updated properties for '${this.node.name}' successfully`,
Β Β Β Β Β Β Β Β Β Β Β 4000);
Β Β Β Β Β Β Β }
Β Β Β Β Β );
Β Β Β } else {
Β Β Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β Β Β `Node '${this.node.name}' was NOT saved, nothing has been changed!`,
Β Β Β Β Β Β Β 4000);
Β Β Β }
Β }
Β private navigateBack2DocList() {
Β Β Β this.router.navigate(['../../'],
Β Β Β Β Β {
Β Β Β Β Β Β Β queryParams: {current_folder_id: this.parentFolder.id},
Β Β Β Β Β Β Β relativeTo: this.activatedRoute
Β Β Β Β Β });
Β }
Β private download(url: string, fileName: string) {
Β Β Β if (url && fileName) {
Β Β Β Β Β const link = document.createElement('a');
Β Β Β Β Β link.style.display = 'none';
Β Β Β Β Β link.download = fileName;
Β Β Β Β Β link.href = url;
Β Β Β Β Β document.body.appendChild(link);
Β Β Β Β Β link.click();
Β Β Β Β Β document.body.removeChild(link);
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe fist two properties nodeΒ and parentFolder are the same as we had when implementing the Card View based Details page. However, instead of keeping an array of card view items we keep the form object and the originalFormData object. The form object will be created based on the form definition JSON that we downloaded and stored in the AlfrescoNodeForm class and the originalFormData.Β
The ngOnInit function is also almost identical to the one we have in the Card View implementation, it just fetches the Alfresco Repo node that we are going to show in the form. But, instead of calling setupProps we call setupFormData,Β which is a rewritten setupProps function that instead sets up the originalFormData that we load the form with and then uses the Form Service to parse the form definition JSON we got from theΒ AlfrescoNodeForm.getDefinition() call.
The onGoBack, onDownload, and onDelete event handler functions are the same as for the Card View implementation. The onSave function looks a lot like the one we had in the Card View implementation except here we get the properties we are about to update from the form field values.
TheΒ navigateBack2DocList function has been changed a little bit as the route to the Form Details page is /form/<node-id> and not just /<node-id>. So we have to navigate two steps up with ../../
Β
Last thing to note here is that I have started to create a Repository Form Field model that will contain all constants for form field names etc. Create theΒ src/app/repository/repository-formfield.model.ts file and put the following into it:
/**
* Form field identifiers when working with ADF Forms
*/
export class RepositoryFormFieldModel {
Β static readonly TITLE_FIELD_NAME = 'title';
Β static readonly DESC_FIELD_NAME = 'description';
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββFor the preview to work in this details page we also need to copy the CSS fromΒ the src/app/repository/repository-details-page/repository-details-page.component.cssΒ style file to theΒ src/app/repository/repository-details-form-page/repository-details-form-page.component.css file.
Adding the Form Details content action to the Repository List page
The Form Details page is now finished and we can create a content action in the List page that takes you to it. We want to implement aΒ Details(form)Β content action that looks like this:

When the user clicks the Details action for an item in the list the form details page route should be activated (e.g.Β http://localhost:4200/repository/form/6bcdad1d-f28f-44ee-9476-b7fdcc964451). First thing we need to do is define the new content action. Open up the src/app/repository/repository-list-page/repository-list-page.component.htmlΒ template file and update it with the new Form Details action, putΒ itΒ just after the other Details actionΒ for folder actions and for document actions:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
Β <adf-upload-button
Β Β Β [rootFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [uploadFolders]="false"
Β Β Β [multipleFiles]="true"
Β Β Β [acceptedFilesType]="'*'"
Β Β Β [versioning]="false"
Β Β Β [adf-node-permission]="'create'"
Β Β Β [adf-nodes]="getNodesForPermissionCheck()"
Β Β Β (success)="onButtonUploadSuccess($event)">
Β </adf-upload-button>
Β <adf-toolbar-divider></adf-toolbar-divider>
Β <button
Β Β Β mat-icon-button
Β Β Β [disabled]="!canCreateContent(documentList.folderNode)"
Β Β Β [adf-create-folder]="getDocumentListCurrentFolderId()">
Β Β Β <mat-icon>create_new_folder</mat-icon>
Β </button>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="getDocumentListCurrentFolderId()"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'folder'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onFolderDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DETAILS_FORM' | translate}}"
Β Β Β Β Β Β Β [icon]="'folder'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onFolderDetailsForm($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DOWNLOAD_AS_ZIP' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onDownloadAsZip($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'insert_drive_file'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β (execute)="onDocumentDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DETAILS_FORM' | translate}}"
Β Β Β Β Β Β Β [icon]="'insert_drive_file'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β (execute)="onDocumentDetailsForm($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
Β Β Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [handler]="'download'">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.COPY' | translate}}"
Β Β Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MOVE' | translate}}"
Β Β Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MANAGE_VERSIONS' | translate}}"
Β Β Β Β Β Β Β [icon]="'storage'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β (execute)="onManageVersions($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe component class then need to implement the two so called custom content actions. They are called custom as they donβt have out-of-the-box handler implementations, such as βdeleteβ. Instead we point to the action implementation via theΒ (execute)Β event. These new action handlers look as follows, add them to theΒ src/app/repository/repository-list-page/repository-list-page.component.tsΒ file:
Β onFolderDetailsForm(event: any) {
Β Β Β const entry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β console.log('RepositoryListPageComponent: Navigating to details page (form) for folder: ' + entry.name);
Β Β Β this.router.navigate(['/repository/form', entry.id]);
Β }
Β onDocumentDetailsForm(event: any) {
Β Β Β const entry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β console.log('RepositoryListPageComponent: Navigating to details page (form) for document: ' + entry.name);
Β Β Β this.router.navigate(['/repository/form', entry.id]);
Β }βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe need these extra functions as we want to navigate to a different Details page than the one we already have for Card View display.Β This should be all that is needed, except the i18n resources for the extra Form Details actions. Add them in theΒ src/assets/i18n/en.jsonΒ file as follows:
{
Β "DOCUMENT_LIST": {
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DETAILS": "Details",
Β Β Β Β Β Β Β "DETAILS_FORM": "Details(form)",
Β Β Β Β Β Β Β "DOWNLOAD_AS_ZIP": "Download as ZIP",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DETAILS": "Details",
Β Β Β Β Β Β Β "DETAILS_FORM": "Details(form)",
Β Β Β Β Β Β Β "DOWNLOAD": "Download",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "MANAGE_VERSIONS": "Manage Versions...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe are now ready to test the new Form Details page implementation.Β
Adding Search to the main toolbar
Our little content management application is starting to take shape. However, we are missing an essential piece of functionality, search. So letβs implement it.
Introduction to the search functionality
What we want is a Search Bar in the main orange toolbar that supports both live/instant search and also supports navigating to a Search Result page. The Search Bar component would look something like this:

The live search result drop down appears automatically as soon as the user has typed 3 or more characters. If youΒ select (i.e. click or hit enter)Β on an item in the list theΒ Repository Details page will beΒ displayed.Β
If you hit enter in the Search Bar input field, then the Search Result page appears:

Notice how, in the URL, the search term is passed on to the search result page as the q parameter (i.e. http://localhost:4200/search;q=project).
Generating a new search module and page components
We are going to need one Search Bar component and one Search Result page component. Plus a search module to keep everything organised.
As usual, we can easily create a new module and the needed components with the Angular CLI tool:
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$Β
Β Β Β Β Β Β Β Β Β ng g module content-search --flat false --routing
Β create src/app/content-search/content-search-routing.module.ts (256 bytes)
Β create src/app/content-search/content-search.module.ts (308 bytes)
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$ cd src/app/content-search/
Martins-MacBook-Pro:search mbergljung$ ng g component search-bar
Β create src/app/content-search/search-bar/search-bar.component.scss (0 bytes)
Β create src/app/content-search/search-bar/search-bar.component.html (29 bytes)
Β create src/app/content-search/search-bar/search-bar.component.spec.ts (650 bytes)
Β create src/app/content-search/search-bar/search-bar.component.ts (285 bytes)
Β update src/app/content-search/content-search.module.ts (398 bytes)
Martins-MacBook-Pro:search mbergljung$ ng g component search-result-page
Β create src/app/content-search/search-result-page/search-result-page.component.scss (0 bytes)
Β create src/app/content-search/search-result-page/search-result-page.component.html (37 bytes)
Β create src/app/content-search/search-result-page/search-result-page.component.spec.ts (700 bytes)
Β create src/app/content-search/search-result-page/search-result-page.component.ts (316 bytes)
Β update src/app/content-search/content-search.module.ts (520 bytes)
This creates a search module with routing and a search-bar component from where to do the searching, and a search result page where we can display the result of a search.
For the ADF Search components and the ADF Viewer component to be available to use inΒ a template we need to import the App Common Module. Open up src/app/content-search/content-search.module.tsΒ file and add it as follows:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContentSearchRoutingModule } from './content-search-routing.module';
import { SearchBarComponent } from './search-bar/search-bar.component';
import { SearchResultPageComponent } from './search-result-page/search-result-page.component';
import { AppCommonModule } from '../app-common/app-common.module';
@NgModule({
Β imports: [
Β Β Β CommonModule,
Β Β Β ContentSearchRoutingModule,
Β Β Β /* Common App imports (Angular Core and Material, ADF Core, Content, and Process */
Β Β Β AppCommonModule
Β ],
Β declarations: [SearchBarComponent, SearchResultPageComponent],
Β exports: [SearchBarComponent]
})
export class ContentSearchModule { }βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββNote here that we need to export the SearchBar component from the ContentSearchModule as it is going to be used outside this module in the main app layout.
</code></pre><p style="font-size: 11pt; padding-top: 0pt; padding-bottom: 0pt;"><span style="color: #000000;">We need a new route set up for the Search Result page. Letβs add it in the src/app/content-search/<strong>content-search-routing.module.ts</strong>Β <span style="font-weight: 400; text-decoration: none; font-size: 11pt;">file as follows:</span></span></p><p style="color: #000000; font-size: 11pt; padding-top: 0pt; padding-bottom: 0pt;"></p><pre class="language-javascript line-numbers"><code>import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SearchResultPageComponent } from './search-result-page/search-result-page.component';
import { AuthGuardEcm } from '@alfresco/adf-core';
const routes: Routes = [
Β { path: 'search',
Β Β Β component: SearchResultPageComponent,
Β Β Β canActivate: [AuthGuardEcm],
Β Β Β data: {
Β Β Β Β Β title: 'Search',
Β Β Β Β Β hidden: true,
Β Β Β Β Β needEcmAuth: true,
Β Β Β Β Β isLogin: false
Β Β Β }
Β }
];
@NgModule({
Β imports: [RouterModule.forChild(routes)],
Β exports: [RouterModule]
})
export class ContentSearchRoutingModule { }ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe route configuration should be familiar from what we have done before and the data object is explained in the second article that is linked to in the introduction. This route only makes sense when you have done a search via the Search Bar, and then want to show the result, so it is set up as hidden. So we don't want to show a left navigation item called βSearchβ.
For theΒ searchΒ components to be known at the app component level we need to importΒ the ADF Search Module intoΒ the App Module file. Open up the src/app/app.module.tsΒ file and add the following modules:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AppCommonModule } from './app-common/app-common.module';
import { AppLoginRoutingModule } from './app-login/app-login-routing.module';
import { AppLoginModule } from './app-login/app-login.module';
import { AppMenuService } from './app-menu/app-menu.service';
import { RepositoryRoutingModule } from './repository/repository-routing.module';
import { RepositoryModule } from './repository/repository.module';
import { ContentSearchRoutingModule} from './content-search/content-search-routing.module';
import { ContentSearchModule} from './content-search/content-search.module';
@NgModule({
Β declarations: [
Β Β Β AppComponent
Β ],
Β imports: [
Β Β Β BrowserModule,
Β Β Β AppRoutingModule,
Β Β Β AppCommonModule,
Β Β Β AppLoginModule,
Β Β Β AppLoginRoutingModule,
Β Β Β RepositoryModule,
Β Β Β RepositoryRoutingModule,
Β Β Β ContentSearchModule,
Β Β Β ContentSearchRoutingModule
Β ],
Β providers: [AppMenuService],
Β bootstrap: [AppComponent]
})
export class AppModule { }ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββNote also that we make the search routing configuration known to the application router.
Implementing the Search Bar component
Now we can move on and implement the Search Bar component that will provide live search and navigation to the Search Result page if you hit enter. Letβs start with the template for it. Open up the src/app/content-search/search-bar/search-bar.component.htmlΒ file and replace whatever is there with the following:
<adf-search-control *ngIf="isLoggedIn()"
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β [autocomplete]="false"
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β [highlight]="true"
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β (submit)="onSearchSubmit($event)"
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β (searchChange)="onSearchTermChange($event)"
Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β (optionClicked)="onItemClicked($event)">
</adf-search-control>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
To implement the Search Bar we use theΒ <adf-search-control. It will provide all of the functionality that we need, including hooks for customisation. The parameters and events have the following meaning:
- inputType (textπ the type of <input field to use for the Search Bar.
- autocomplete (false): set toΒ trueΒ if you want the system to autocomplete search terms as you are typing them in.
- expandable (true): controls if the search bar/input field is expandable or not.
- highlight (false):Β trueΒ means highlight the search term in the search result.
- liveSearchEnabled (true): should live search be possible in the search bar.
- liveSearchMaxResults (5π how many items to show in search result drop down.
- (submitπ function is called when user hits enter in Search Bar field.
- (searchChangeπ function is called when the search term is changed.
- (expandπ can be used to do stuff when the Search Bar input field is expanded or collapsed.
- (optionClickedπ function is called when user clicks on an item in the live search result drop down.
We implement the event handler functions, such as theΒ onSearchSubmit()Β as follows in the src/app/content-search/search-bar/search-bar.component.tsΒ file:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from '@alfresco/adf-core';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Component({
Β selector: 'app-search-bar',
Β templateUrl: './search-bar.component.html',
Β styleUrls: ['./search-bar.component.scss']
})
export class SearchBarComponent implements OnInit {
Β searchTerm = '';
Β constructor(public router: Router,
Β Β Β Β Β Β Β Β Β Β Β Β Β public authService: AuthenticationService) {
Β }
Β ngOnInit() {
Β }
Β isLoggedIn(): boolean {
Β Β Β return this.authService.isLoggedIn();
Β }
Β onSearchSubmit(event: KeyboardEvent) {
Β Β Β const searchTerm = (event.target as HTMLInputElement).value;
Β Β Β this.router.navigate(['/search', {
Β Β Β Β Β 'q': searchTerm
Β Β Β }]);
Β }
Β onItemClicked(node: MinimalNodeEntity) {
Β Β Β console.log('Item clicked: ', node);
Β Β Β this.router.navigate(['/repository', node.entry.id]);
Β }
Β onSearchTermChange(event) {
Β Β Β this.searchTerm = event.value;
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββTheΒ onSearchSubmit()Β function will navigate to the Search Result page component (i.e. /search) with the search term set as parameterΒ q. TheΒ βqβΒ parameterΒ could have any name you like, but traditionally it has been named q, so let's stick with it.
Before version 2.0 of ADF the search result page was pre-built and theΒ parameterΒ name was hardcoded into the Search/Search Result component as q.
The search is actually executed in the Search Result page component with out-of-the-box ADF search components. We will implement the Search Result page in the next section.
If you click on an item in the live search dropdown (onItemClicked()), then the Repository Details page will be displayed both for files and folders. TheΒ onSearchTermChange()Β function just keeps current search term stored in theΒ searchTermΒ variable, so it is always displayed in the Search Bar input field.
Adding the Search Bar to the main toolbar
Now when we have finished the Search Bar we can include it in the main application toolbar 2. Open up the src/app/app.component.htmlΒ file and update it to look as follows:
<mat-sidenav-container>
Β <mat-sidenav #mainSideNav mode="side" opened>
Β Β Β <mat-toolbar>
Β Β Β Β Β <img src="../assets/images/alfresco-logo.png" style="height:60%">
Β Β Β Β Β <span fxFlex></span>
Β Β Β Β Β {{ appName }}
Β Β Β </mat-toolbar>
Β Β Β <mat-nav-list>
Β Β Β Β Β <a *ngFor="let menuItem of mainMenuItems"
Β Β Β Β Β Β Β Β mat-list-item
Β Β Β Β Β Β Β Β mat-ripple
Β Β Β Β Β Β Β Β [style.position]="'relative'"
Β Β Β Β Β Β Β Β routerLinkActive="selected"
Β Β Β Β Β Β Β Β [routerLink]="[menuItem.path]">
Β Β Β Β Β Β Β <mat-icon mat-list-icon *ngIf="menuItem.icon">{{ menuItem.icon }}</mat-icon>
Β Β Β Β Β Β Β <span>{{ menuItem.title }}</span>
Β Β Β Β Β </a>
Β Β Β </mat-nav-list>
Β </mat-sidenav>
Β <mat-toolbar color="primary">
Β Β Β <button mat-icon-button (click)="mainSideNav.toggle()">
Β Β Β Β Β <mat-icon *ngIf="mainSideNav.opened">chevron_left</mat-icon>
Β Β Β Β Β <mat-icon *ngIf="!mainSideNav.opened">menu</mat-icon>
Β Β Β </button>
Β Β Β {{ (activeMenuItem$ | async)?.title }}
Β Β Β <span fxFlex></span>
Β Β Β <app-search-bar></app-search-bar>
Β Β Β <span fxFlex></span>
Β Β Β {{ getCurrentUser() }}
Β Β Β <button mat-icon-button [matMenuTriggerFor]="dropdownMenu">
Β Β Β Β Β <mat-icon>more_vert</mat-icon>
Β Β Β </button>
Β </mat-toolbar>
Β <router-outlet></router-outlet>
</mat-sidenav-container>
<mat-menu #dropdownMenu x-position="before">
Β <a mat-menu-item href="" (click)="onLogout($event)">
Β Β Β <mat-icon>exit_to_app</mat-icon>
Β Β Β <span>Logout</span>
Β </a>
Β <a mat-menu-item href="" routerLink="/about">
Β Β Β <mat-icon>info_outline</mat-icon>
Β Β Β <span>About</span>
Β </a>
</mat-menu>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββTo inject the Search Bar we use theΒ <app-search-bar>Β selector that was set when the component was generated. There are also someΒ spanΒ tags with flex styling around it so it ends up in the middle of the toolbar.
The Search Bar should now be visible in the main orange toolbar:

Update the Repository Details page component to read node-id async
When we select an item (i.e. file or folder) in the Search Bar result drop down and it takes you to the Repository Details page it works nicely the first time around. However, if we do a search again and select an item the Details page is not updated. This is because we currently read the node-id parameter from the Repository Details page URL (e.g. http://localhost:4200/repository/80ca4db6-85dd-43aa-a3d9-84c2fe321aaa) synchronously as follows in theΒ src/app/repository/repository-details-page/repository-details-page.component.ts file:
export class RepositoryDetailsPageComponent implements OnInit {
...
Β ngOnInit() {
Β Β Β const nodeId = this.activatedRoute.snapshot.params['node-id'];ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββIf we keep this way of extracting the node identifier (i.e. Alfresco Node Reference), it will not work when the page is calledΒ multiple timesΒ with just a different node-id parameter. We need to update it to fetch the parameter synchronously as follows:
ngOnInit() {
Β this.activatedRoute.params.subscribe(params => {
Β Β Β const nodeId = params['node-id'];
Β Β Β console.log('Node ID: ', nodeId);
Β Β Β this.nodeService.getNode(nodeId).subscribe((entry: MinimalNodeEntryEntity) => {
Β Β Β Β Β this.node = entry;
Β Β Β Β Β this.nodeService.getNode(this.node.parentId).subscribe((parentNode: MinimalNodeEntryEntity) => {
Β Β Β Β Β Β Β this.parentFolder = parentNode;
Β Β Β Β Β });
Β Β Β Β Β this.setupProps(this.node);
Β Β Β });
Β });
Β this.cardViewUpdateService.itemUpdated$.subscribe(this.updateNodeDetails.bind(this));
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
Implementing the Search Result component
Last thing we need to do is implement the Search Result page component. Which actually also does the search based on the search term parameter that it receives (e.g.Β http://localhost:4200/search;q=project). Open up the template located in the src/app/content-search/search-result-page/search-result-page.component.htmlΒ file and update to look like this:
<adf-search [searchTerm]="searchTerm"
Β Β Β Β Β Β Β Β Β Β Β [maxResults]="20"
Β Β Β Β Β Β Β Β Β Β Β (resultLoaded)="showSearchResult($event)">
</adf-search>
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β Search Results
Β </adf-toolbar-title>
</adf-toolbar>
<adf-document-list
Β #documentList
Β [navigationMode]="'click'"
Β [currentFolderId]="null"
Β [contextMenuActions]="true"
Β [contentActions]="true"
Β [node]="resultNodePageList">
Β <content-actions>
Β Β Β <!-- Folder actions -->
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DETAILS' | translate}}"
Β Β Β Β Β [icon]="'folder'"
Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β (execute)="onDetails($event)">
Β Β Β </content-action>
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.COPY' | translate}}"
Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β </content-action>
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.MOVE' | translate}}"
Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β </content-action>
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β </content-action>
Β Β Β <!-- File actions -->
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DETAILS' | translate}}"
Β Β Β Β Β [icon]="'insert_drive_file'"
Β Β Β Β Β [target]="'document'"
Β Β Β Β Β (execute)="onDetails($event)">
Β Β Β </content-action>
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
Β Β Β Β Β [icon]="'file_download'"
Β Β Β Β Β [target]="'document'"
Β Β Β Β Β [handler]="'download'">
Β Β Β </content-action>
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.COPY' | translate}}"
Β Β Β Β Β [icon]="'content_copy'"
Β Β Β Β Β [target]="'document'"
Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β [handler]="'copy'"
Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β </content-action>
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.MOVE' | translate}}"
Β Β Β Β Β [icon]="'redo'"
Β Β Β Β Β [target]="'document'"
Β Β Β Β Β [permission]="'update'"
Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β [handler]="'move'"
Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β </content-action>
Β Β Β <content-action
Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β [target]="'document'"
Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β </content-action>
Β </content-actions>
Β <empty-folder-content>
Β Β Β <ng-template>
Β Β Β Β Β <div class="adf-empty_template">
Β Β Β Β Β Β Β <div class="adf-no-result-message">{{ 'DOCUMENT_LIST.SEARCH.NO_RESULT' | translate }}</div>
Β Β Β Β Β </div>
Β Β Β </ng-template>
Β </empty-folder-content>
</adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
<adf-pagination
Β [target]="documentList">
</adf-pagination>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe Search is implemented with theΒ <adf-searchΒ component. There are a number of parameters that you can configure for this component. In our case we want to customise what happens when the search result is available (i.e. implement the resultLoaded event handler). We then just want to take the list of nodes that was returned in the search result and display them in an ADF Document List.
In ADF versions prior to 2.0 the <adf-searchΒ component had a built in view based on Document List. This is no longer the case and we now have to build the search result view, giving us greater flexibility.
You will be familiar with the document list template code (i.e. <adf-document-list) as it is copied from the Repository List template. I took out some of the content actions. If the search result is empty we will display the following view via the <empty-folder-content component:

The markup for the empty result page uses some CSS classes that we define in theΒ src/app/search/search-result-page/search-result-page.component.scssΒ file as follows:
.adf-empty_template {
Β text-align: center;
Β margin-top: 20px;
Β margin-bottom: 20px;
}
.adf-no-result-message{
Β height: 32px;
Β opacity: 0.26;
Β font-size: 24px;
Β line-height: 1.33;
Β letter-spacing: -1px;
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe search result component implementation is done inΒ the src/app/content-search/search-result-page/search-result-page.component.tsΒ file as follows:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { MinimalNodeEntryEntity, NodePaging } from 'alfresco-js-api';
import { NotificationService } from '@alfresco/adf-core';
@Component({
Β selector: 'app-search-result-page',
Β templateUrl: './search-result-page.component.html',
Β styleUrls: ['./search-result-page.component.scss']
})
export class SearchResultPageComponent implements OnInit {
Β queryParamName = 'q';
Β searchTerm = '';
Β resultNodePageList: NodePaging;
Β constructor(private activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β Β Β Β private router: Router,
Β Β Β Β Β Β Β Β Β Β Β Β Β private notificationService: NotificationService) { }
Β ngOnInit() {
Β Β Β if (this.activatedRoute) {
Β Β Β Β Β this.activatedRoute.params.forEach((params: Params) => {
Β Β Β Β Β Β Β this.searchTerm = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
Β Β Β Β Β });
Β Β Β }
Β }
Β showSearchResult(event: NodePaging) {
Β Β Β this.resultNodePageList = event;
Β }
Β onDetails(event: any) {
Β Β Β const entry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β this.router.navigate(['/repository', entry.id]);
Β }
Β onContentActionPermissionError(event: any) {
Β Β Β this.notificationService.openSnackMessage(
Β Β Β Β Β `You don't have the '${event.permission}' permission to do a '${event.action}' operation on the ${event.type}`,
Β Β Β Β Β 4000);
Β }
Β onContentActionSuccess(nodeId) {
Β Β Β console.log('Successfully executed content action for node: ' + nodeId);
Β }
Β onContentActionError(error) {
Β Β Β console.log('There was an error executing content action: ' + error);
Β }
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββIn the ngOnInit we read the search term from the URL parameter q. When a search for the search term is executed by the <adf-search component and a result is available, then the showSearchResult function is called. It just sets the nodes that was returned in the resultNodePageList variable. This variable is then fed into the Document list as follows:
...
<adf-document-list
Β #documentList
Β [navigationMode]="'click'"
Β [currentFolderId]="null"
Β [contextMenuActions]="true"
Β [contentActions]="true"
Β [node]="resultNodePageList">
Β <content-actions>
...ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Note how the currentFolderId is set to null to indicate to the Document List component that we don't want it to handle fetching the node data, we will inject it via the node property.
This should be all that is needed for the search functionality to work.Β
Working withΒ My Files
In this section we will see how we can add another page with a document list that shows our personal files in the repository. We call these files My Files.
Introduction to My Files
My Files are basically the content that each Alfresco user has underΒ /Company Home/User Homes/<userid>. When displaying our own files we want a bit different layout when it comes to the data columns that we use compared to when displaying the Repository (i.e. All Files). For example, we donβt need to display the Created By and Created At columns as it is mostly likely ourselves who created the file or folder, so we already know this. We also want to display the version of a file, plus some other changes. It will look something like this when finished:

So the list of columns start with the name of the content item, then the title and description from theΒ cm:titledΒ aspect, last updated date (i.e. modified at), size, and version. The size should not be displayed for folders, and the same for version, folder are not versioned.
When we implement this we want to reuse as much as possible from the Repository List page.Β When building the My Files document list view we will use custom data columns, so it is also worth looking in more detail at theΒ <adf-datatable.
GeneratingΒ the My Files module and page components
We are going to need a My Files parent page component and a list page component. Plus a module to keep everything organised. Note that we don't need a details page component as we will reuse the one from the Repository implementation.
As usual, we can easily create a new module and the needed components with the Angular CLI tool. Standing in the adf-workbench-content20 directory:
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$ ng g module my-files --flat false --routing
Β create src/app/my-files/my-files-routing.module.ts (250 bytes)
Β create src/app/my-files/my-files.module.ts (284 bytes)
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$ cd src/app/my-files
Martins-MacBook-Pro:my-files mbergljung$ ng g component my-files-page
Β create src/app/my-files/my-files-page/my-files-page.component.css (0 bytes)
Β create src/app/my-files/my-files-page/my-files-page.component.html (32 bytes)
Β create src/app/my-files/my-files-page/my-files-page.component.spec.ts (665 bytes)
Β create src/app/my-files/my-files-page/my-files-page.component.ts (295 bytes)
Β update src/app/my-files/my-files.module.ts (384 bytes)
Martins-MacBook-Pro:my-files mbergljung$ ng g component my-files-list-page
Β create src/app/my-files/my-files-list-page/my-files-list-page.component.css (0 bytes)
Β create src/app/my-files/my-files-list-page/my-files-list-page.component.html (37 bytes)
Β create src/app/my-files/my-files-list-page/my-files-list-page.component.spec.ts (694 bytes)
Β create src/app/my-files/my-files-list-page/my-files-list-page.component.ts (314 bytes)
Β update src/app/my-files/my-files.module.ts (504 bytes)
This creates a my-files module with routing, a my-files parent page, and a my-files list page component where we will display the My Files Document List.
We need to set up a new route configuration similar to what we did for Repository. A parent route with two child routes for list and detail pages. Letβs add it in the src/app/my-files/my-files-routing.module.tsΒ file as follows:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RepositoryDetailsPageComponent } from '../repository/repository-details-page/repository-details-page.component';
import { MyFilesListPageComponent } from './my-files-list-page/my-files-list-page.component';
import { MyFilesPageComponent } from './my-files-page/my-files-page.component';
import { AuthGuardEcm } from '@alfresco/adf-core';
const routes: Routes = [ {
Β path: 'my-files',
Β component: MyFilesPageComponent,
Β canActivate: [AuthGuardEcm],
Β data: {
Β Β Β title: 'My Files',
Β Β Β icon: 'folder_shared',
Β Β Β hidden: false,
Β Β Β needEcmAuth: true,
Β Β Β isLogin: false
Β },
Β children: [
Β Β Β { path: '', component: MyFilesListPageComponent, canActivate: [AuthGuardEcm] },
Β Β Β { path: ':node-id', component: RepositoryDetailsPageComponent, canActivate: [AuthGuardEcm]Β }
Β ]
}];
@NgModule({
Β imports: [RouterModule.forChild(routes)],
Β exports: [RouterModule]
})
export class MyFilesRoutingModule { }ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe cool thing here is that we will reuse the Repository Details page. We most likely want to see a detail view of our files too, and we donβt want to write it from scratch.Β The route configuration should be familiar and straightforward by now. This parent route is not hidden and should appear in the left navigation section.
We also need to make the new page and route known to the main app. Open up the src/app/app.module.tsΒ file and add as follows:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AppCommonModule } from './app-common/app-common.module';
import { AppLoginRoutingModule } from './app-login/app-login-routing.module';
import { AppLoginModule } from './app-login/app-login.module';
import { AppMenuService } from './app-menu/app-menu.service';
import { RepositoryRoutingModule } from './repository/repository-routing.module';
import { RepositoryModule } from './repository/repository.module';
import { ContentSearchRoutingModule} from './content-search/content-search-routing.module';
import { ContentSearchModule} from './content-search/content-search.module';
import { MyFilesModule } from './my-files/my-files.module';
import { MyFilesRoutingModule } from './my-files/my-files-routing.module';
@NgModule({
Β declarations: [
Β Β Β AppComponent
Β ],
Β imports: [
Β Β Β BrowserModule,
Β Β Β AppRoutingModule,
Β Β Β AppCommonModule,
Β Β Β AppLoginModule,
Β Β Β AppLoginRoutingModule,
Β Β Β RepositoryModule,
Β Β Β RepositoryRoutingModule,
Β Β Β ContentSearchModule,
Β Β Β ContentSearchRoutingModule,
Β Β Β MyFilesModule,
Β Β Β MyFilesRoutingModule
Β ],
Β providers: [AppMenuService],
Β bootstrap: [AppComponent]
})
export class AppModule { }ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββImplementing the My FilesΒ Parent component
The parent component should only be providing a <router-outlet>. Add it to theΒ src/app/my-files/my-files-page/my-files-page.component.html file as follows:
<router-outlet></router-outlet>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Implementing the My Files List component
Now we can move on and implement the My Files List component that will provide a Document List of the files and folders that exists in theΒ /Company Home/User Homes/<userid>Β folder. Β Letβs start with the page template. Open up the src/app/my-files/my-files-list-page/my-files-list-page.component.htmlΒ file and replace whatever is there with the following:
<adf-toolbar [color]="'accent'">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
Β <adf-upload-button
Β Β Β [rootFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [uploadFolders]="false"
Β Β Β [multipleFiles]="true"
Β Β Β [acceptedFilesType]="'*'"
Β Β Β [versioning]="false"
Β Β Β [adf-node-permission]="'create'"
Β Β Β [adf-nodes]="getNodesForPermissionCheck()"
Β Β Β (success)="onButtonUploadSuccess($event)">
Β </adf-upload-button>
Β <adf-toolbar-divider></adf-toolbar-divider>
Β <button
Β Β Β mat-icon-button
Β Β Β [disabled]="!canCreateContent(documentList.folderNode)"
Β Β Β [adf-create-folder]="getDocumentListCurrentFolderId()">
Β Β Β <mat-icon>create_new_folder</mat-icon>
Β </button>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="getDocumentListCurrentFolderId()"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <data-columns>
Β Β Β Β Β <data-column key="$thumbnail" type="image"></data-column>
Β Β Β Β Β <data-column title="{{'DOCUMENT_LIST.COLUMNS.NAME' | translate}}" key="name" class="full-width ellipsis-cell"></data-column>
Β Β Β Β Β <data-column title="{{'DOCUMENT_LIST.COLUMNS.TITLE' | translate}}" key="properties.cm:title" type="text"></data-column>
Β Β Β Β Β <data-column title="{{'DOCUMENT_LIST.COLUMNS.DESCRIPTION' | translate}}" key="properties.cm:description" type="text"></data-column>
Β Β Β Β Β <data-column title="{{'DOCUMENT_LIST.COLUMNS.LAST_UPDATED' | translate}}" key="modifiedAt" type="date" format="shortDate" ></data-column>
Β Β Β Β Β <data-column title="{{'DOCUMENT_LIST.COLUMNS.SIZE' | translate}}" key="content.sizeInBytes" type="fileSize"></data-column>
Β Β Β Β Β <data-column title="{{'DOCUMENT_LIST.COLUMNS.VERSION' | translate}}" key="properties.cm:versionLabel">
Β Β Β Β Β Β Β <ng-template let-value="value">
Β Β Β Β Β Β Β Β Β <span *ngIf="value">V. {{value}}</span>
Β Β Β Β Β Β Β </ng-template>
Β Β Β Β Β </data-column>
Β Β Β </data-columns>
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'folder'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onFolderDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'insert_drive_file'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β (execute)="onDocumentDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWe can see here that this template looks a lot like theΒ one we did before inΒ the src/app/repository/repository-list-page/repository-list-page.component.htmlΒ file. This is because we want the same layout for the My Files List page as for the Repository List page. We want a toolbar at the top with upload button and create folder button. We want to be able to drag-and-drop files into My Files folder. However, we want less content actions (if you need all actions, just copy them over from Repository List...). The only real difference is the data column layout, and you can see the newΒ <data-columns>Β markup for it above.
Each Document List column is represented by aΒ <data-column>Β element with a number of attributes. The most significant one is theΒ key, which is the name of the property whose value is to be displayed. We can choose from the following properties of theΒ MinimalNodeΒ class, which in turn extends theΒ NodeΒ classΒ that looksΒ like this:
export class Node {
Β constructor(obj?: any);
Β id?: string;
Β name?: string;
Β nodeType?: string;
Β isFolder?: boolean;
Β isFile?: boolean;
Β isLocked?: boolean;
Β modifiedAt?: Date;
Β modifiedByUser?: UserInfo;
Β adddAt?: Date;
Β adddByUser?: UserInfo;
Β parentId?: string;
Β isLink?: boolean;
Β content?: ContentInfo;
Β aspectNames?: Array<string>;
Β properties?: any;
Β allowableOperations?: Array<string>;
Β path?: PathInfo;
Β permissions?: PermissionsInfo;
Β createdAt?: Date;
Β createdByUser?: UserInfo;
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββSome properties are directly accessible via their name, such asΒ nameΒ andΒ modifiedAt. To reach other properties you need to use a path, such as when accessing fileΒ contentΒ data:
export interface ContentInfo {
Β Β mimeType?: string;
Β Β mimeTypeName?: string;
Β Β sizeInBytes?: number;
Β Β encoding?: string;
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββTo access the size of a content file we useΒ content.sizeInBytes. We can also access other properties that are part of different types of aspects (i.e. secondary types). We access both the title (cm:title) and description (cm:description) from theΒ cm:titledΒ aspect. Note the notation to access these properties,Β properties.<content model namespace>:localname.Β For example:Β properties.cm:description.
It is possible to customize how the value should be displayed for a column. For example, we display the version label with a V in front of it by using the following data column configuration:
<data-column title="{{'DOCUMENT_LIST.COLUMNS.VERSION' | translate}}" key="properties.cm:versionLabel">
Β <ng-template let-value="value">
Β Β Β Β <span *ngIf="value">V. {{value}}</span>
Β </ng-template>
</data-column>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββHere we first create a variable for the property value calledΒ value. We do this with theΒ let-valueΒ construct. Then we useΒ ng-templateΒ to output our own view of the property value. We also donβt display theΒ valueΒ if it is undefined, by usingΒ ngIf, which means it will not be displayed asΒ V.Β for folder nodes.
The title of a data column is fetched from our i18n resource file via theΒ title="{{'DOCUMENT_LIST.COLUMNS.<name> | translate}}"Β construct. Open up theΒ src/assets/i18n/en.jsonΒ file and update the column titles as follows:
{
Β "DOCUMENT_LIST": {
Β Β Β "SEARCH": {
Β Β Β Β Β "NO_RESULT": "0 - results found"
Β Β Β },
Β Β Β "COLUMNS": {
Β Β Β Β Β "NAME": "Name",
Β Β Β Β Β "TITLE": "Title",
Β Β Β Β Β "DESCRIPTION": "Description",
Β Β Β Β Β "LAST_UPDATED": "Last Updated",
Β Β Β Β Β "SIZE": "Size",
Β Β Β Β Β "VERSION": "Version"
Β Β Β },
Β Β Β "ACTIONS": {
Β Β Β Β Β "FOLDER": {
Β Β Β Β Β Β Β "DETAILS": "Details",
Β Β Β Β Β Β Β "DETAILS_FORM": "Details(form)",
Β Β Β Β Β Β Β "DOWNLOAD_AS_ZIP": "Download as ZIP",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β },
Β Β Β Β Β "DOCUMENT": {
Β Β Β Β Β Β Β "DETAILS": "Details",
Β Β Β Β Β Β Β "DETAILS_FORM": "Details(form)",
Β Β Β Β Β Β Β "DOWNLOAD": "Download",
Β Β Β Β Β Β Β "COPY": "Copy...",
Β Β Β Β Β Β Β "MOVE": "Move...",
Β Β Β Β Β Β Β "MANAGE_VERSIONS": "Manage Versions...",
Β Β Β Β Β Β Β "DELETE": "Delete"
Β Β Β Β Β }
Β Β Β }
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThat should do it as far as the page template goes.
Β
Now we need to implement the backing component class. We donβt want to implement everything from scratch like we did for the Repository List page component. Instead we want to reuse as much as possible. Open up the src/app/my-files/my-files-list-page/my-files-list-page.component.tsΒ file and update it to look as follows:
import { Component, OnInit } from '@angular/core';
import { RepositoryListPageComponent } from '../../repository/repository-list-page/repository-list-page.component';
import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material';
import { NotificationService, ContentService } from '@alfresco/adf-core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
@Component({
Β selector: 'app-my-files-list-page',
Β templateUrl: './my-files-list-page.component.html',
Β styleUrls: ['./my-files-list-page.component.scss']
})
export class MyFilesListPageComponent extends RepositoryListPageComponent implements OnInit {
Β currentFolderId = '-my-'; // By default display /Company Home/User Homes/<userid>
Β constructor(notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β Β Β Β contentService: ContentService,
Β Β Β Β Β Β Β Β Β Β Β Β Β dialog: MatDialog,
Β Β Β Β Β Β Β Β Β Β Β Β Β activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β Β Β Β router: Router) {
Β Β Β super(notificationService, contentService, dialog , activatedRoute, router);
Β }
Β ngOnInit() {
Β Β Β super.ngOnInit();
Β }
Β onFolderDetails(event: any) {
Β Β Β const entry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β console.log('MyFilesListPageComponent: Navigating to details page for folder: ' + entry.name);
Β Β Β this.router.navigate(['/my-files', entry.id]);
Β }
Β onDocumentDetails(event: any) {
Β Β Β const entry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β console.log('MyFilesListPageComponent: Navigating to details page for document: ' + entry.name);
Β Β Β this.router.navigate(['/my-files', entry.id]);
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe main thing to note here is that we are extending and reusing the functionality we implemented for the Repository List page component. As usual when extending another class we need to make sure the correct constructor is called with the super(...) call. We also make sure to call the superΒ ngOnInitΒ function. Then we do the fundamental change that will make the document list display theΒ /Company Home/User Homes/<userid>Β folder, which is My Files. We set theΒ currentFolderIdΒ to the pre configured data storeΒ -my-.
When the user clicks theΒ DetailsΒ content action in the My Files list we want the route to be fromΒ my-filesΒ parent to the reused Repository Details page. So we override theΒ onFolderDetailsΒ andΒ onDocumentDetailsΒ functions. This way when you do go back from the details page you will end up again in the My Files list.
We have also done another important update to theΒ RepositoryListPageComponentΒ constructor. We changed all the injected objects to be protected instead of private:
constructor(protected notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β protected contentService: AlfrescoContentService,
Β Β Β Β Β Β Β Β Β Β protected dialog: MdDialog,
Β Β Β Β Β Β Β Β Β Β protected activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β protected router: Router) {
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
Thatβs pretty much it. The only thing left to do now is to import the components we are using, such as Document List, into the My Files module. Open up the src/app/my-files/my-files.module.tsΒ file and add the following:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MyFilesRoutingModule } from './my-files-routing.module';
import { MyFilesPageComponent } from './my-files-page/my-files-page.component';
import { MyFilesListPageComponent } from './my-files-list-page/my-files-list-page.component';
import { AppCommonModule } from '../app-common/app-common.module';
@NgModule({
Β imports: [
Β Β Β CommonModule,
Β Β Β MyFilesRoutingModule,
Β Β Β /* Common App imports (Angular Core and Material, ADF Core, Content, and Process */
Β Β Β AppCommonModule
Β ],
Β declarations: [MyFilesPageComponent, MyFilesListPageComponent]
})
export class MyFilesModule { }
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWorking with Alfresco Share Sites
Most Alfresco projects today uses Alfresco Share Sites to organize content. So we need to know a bit about how to work with site content in ADF. In this section we will implement a Repository view where the top level is the available sites. The user can then navigate into one of these siteβs Document Library.
There is only limited support for working with Alfresco Share sitesin ADF. Basically you can list the sites and get access to the Document Library. The extra content types, such as Calendar and Data Lists, are not supported. And theyΒ might never be supported as a lot of the Site functionality has been deprecated in ACS.
Introduction to Sites
Share Sites are basically the content that each Alfresco Repository has under theΒ /Company Home/SitesΒ folder. Each site has a top level folder under this folder. And under the top level folder are so called containers for different types of content, such as a Document Library container for managing folders and files.
When displaying sites in the application we want to be able to switch between sites via a drop down box inΒ a toolbar. It will look something like this when finished:

The toolbar with Upload file and Create folder buttons will be disabled when listing the sites.
Clicking theΒ Site ListΒ drop down displays all the available sites that the user has permission to see (in this screenshot we are logged in as admin, and hence can see everything, even private sites):

Selecting one of the sites takes you directly into the Document Library for that site, which is where you usually want to work with folders and files:

We will use the default document list column layout when working within the site Document Library. An extra toolbar will be added to host the new Site List drop down to the left.
When we implement this we want to reuse as much as possible from the Repository List page and the Repository Details page. The ADF βsite controlβ in theΒ newΒ toolbar is available in the ADF Core library.
Generating new Sites module and page components
We are going to need a Sites parent page component and a Sites list page child component. Plus a module to keep everything organized.
As usual, we can easily create a new module and the needed components with the Angular CLI tool. Standing in the adf-workbench-content20 do:
Martins-MacBook-Pro:adf-workbench-content20Β mbergljung$ ng g module sites --flat false --routing
Β create src/app/sites/sites-routing.module.ts (248 bytes)
Β create src/app/sites/sites.module.ts (275 bytes)
Martins-MacBook-Pro:adf-workbench-content20 mbergljung$ cd src/app/sites/
Martins-MacBook-Pro:sites mbergljung$ ng g component sites-page
Β create src/app/sites/sites-page/sites-page.component.css (0 bytes)
Β create src/app/sites/sites-page/sites-page.component.html (29 bytes)
Β create src/app/sites/sites-page/sites-page.component.spec.ts (650 bytes)
Β create src/app/sites/sites-page/sites-page.component.ts (284 bytes)
Β update src/app/sites/sites.module.ts (365 bytes)
Martins-MacBook-Pro:sites mbergljung$ ng g component sites-list-page
Β create src/app/sites/sites-list-page/sites-list-page.component.css (0 bytes)
Β create src/app/sites/sites-list-page/sites-list-page.component.html (34 bytes)
Β create src/app/sites/sites-list-page/sites-list-page.component.spec.ts (679 bytes)
Β create src/app/sites/sites-list-page/sites-list-page.component.ts (303 bytes)
Β update src/app/sites/sites.module.ts (475 bytes)
This creates a sites module with routing, a sites parent page, and a sites list page component where we will display the Sites List.
We need to set up a new route configuration similar to what we did for Repository and My Files. A parent route with two child routes for list and detail pages. Letβs add it in the src/app/sites/sites-routing.module.tsΒ file as follows:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SitesPageComponent } from './sites-page/sites-page.component';
import { SitesListPageComponent } from './sites-list-page/sites-list-page.component';
import { RepositoryDetailsPageComponent } from '../repository/repository-details-page/repository-details-page.component';
import { AuthGuardEcm } from '@alfresco/adf-core';
const routes: Routes = [{
Β path: 'sites',
Β component: SitesPageComponent,
Β canActivate: [AuthGuardEcm],
Β data: {
Β Β Β title: 'Sites',
Β Β Β icon: 'group_work',
Β Β Β hidden: false,
Β Β Β needEcmAuth: true,
Β Β Β isLogin: false
Β },
Β children: [
Β Β Β { path: '', component: SitesListPageComponent, canActivate: [AuthGuardEcm] },
Β Β Β { path: ':node-id', component: RepositoryDetailsPageComponent, canActivate: [AuthGuardEcm] }
Β ]
}];
@NgModule({
Β imports: [RouterModule.forChild(routes)],
Β exports: [RouterModule]
})
export class SitesRoutingModule { }ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββAs with the My Files implementation,Β we will reuse the Repository Details page as a details page for Site Document Library content. The route configuration should be familiar and straightforward by now. This parent route is not hidden and should appear in the left navigation section.
We also need to make the new page and route known to the main app. Open up the src/app/app.module.tsΒ file and add as follows:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AppCommonModule } from './app-common/app-common.module';
import { AppLoginRoutingModule } from './app-login/app-login-routing.module';
import { AppLoginModule } from './app-login/app-login.module';
import { AppMenuService } from './app-menu/app-menu.service';
import { RepositoryRoutingModule } from './repository/repository-routing.module';
import { RepositoryModule } from './repository/repository.module';
import { ContentSearchRoutingModule} from './content-search/content-search-routing.module';
import { ContentSearchModule} from './content-search/content-search.module';
import { MyFilesModule } from './my-files/my-files.module';
import { MyFilesRoutingModule } from './my-files/my-files-routing.module';
import { SitesRoutingModule } from './sites/sites-routing.module';
import { SitesModule } from './sites/sites.module';
@NgModule({
Β declarations: [
Β Β Β AppComponent
Β ],
Β imports: [
Β Β Β BrowserModule,
Β Β Β AppRoutingModule,
Β Β Β AppCommonModule,
Β Β Β AppLoginModule,
Β Β Β AppLoginRoutingModule,
Β Β Β RepositoryModule,
Β Β Β RepositoryRoutingModule,
Β Β Β ContentSearchModule,
Β Β Β ContentSearchRoutingModule,
Β Β Β MyFilesModule,
Β Β Β MyFilesRoutingModule,
Β Β Β SitesModule,
Β Β Β SitesRoutingModule
Β ],
Β providers: [AppMenuService],
Β bootstrap: [AppComponent]
})
export class AppModule { }ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββImplementing the SitesΒ Parent component
The parent component just provides the router-outlet.Β Open up the src/app/sites/sites-page/sites-page.component.htmlΒ file and replace whatever is there with the following:
<router-outlet></router-outlet>ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Implementing the Sites List component
Now we can move on and implement the Sites List component that will provide a Document List of the sites at the top level and a Document List of the files and folders that exists in theΒ /Company Home/Sites/<id>/documentLibraryΒ folder for a site. Β Letβs start with the page template. Open up the src/app/sites/sites-list-page/sites-list-page.component.htmlΒ file and replace whatever is there with the following:
<adf-toolbar>
Β <div class="app-site-container-style">
Β Β Β <adf-sites-dropdown
Β Β Β Β Β [hideMyFiles]="true"
Β Β Β Β Β (change)="getSiteContent($event)">
Β Β Β </adf-sites-dropdown>
Β </div>
</adf-toolbar>
<adf-toolbar [color]="'accent'" *ngIf="!siteListing()">
Β <adf-toolbar-title>
Β Β Β <adf-breadcrumb
Β Β Β Β Β root="Company Home"
Β Β Β Β Β [target]="documentList"
Β Β Β Β Β [folderNode]="documentList.folderNode">
Β Β Β </adf-breadcrumb>
Β </adf-toolbar-title>
Β <adf-upload-button
Β Β Β [rootFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [uploadFolders]="false"
Β Β Β [multipleFiles]="true"
Β Β Β [acceptedFilesType]="'*'"
Β Β Β [versioning]="false"
Β Β Β [adf-node-permission]="'create'"
Β Β Β [adf-nodes]="getNodesForPermissionCheck()"
Β Β Β (success)="onButtonUploadSuccess($event)">
Β </adf-upload-button>
Β <adf-toolbar-divider></adf-toolbar-divider>
Β <button
Β Β Β mat-icon-button
Β Β Β [disabled]="!canCreateContent(documentList.folderNode)"
Β Β Β [adf-create-folder]="getDocumentListCurrentFolderId()">
Β Β Β <mat-icon>create_new_folder</mat-icon>
Β </button>
</adf-toolbar>
<adf-upload-drag-area
Β [parentId]="getDocumentListCurrentFolderId()"
Β [adf-node-permission]="'create'"
Β [adf-nodes]="getNodesForPermissionCheck()"
Β (success)="onDragAndDropUploadSuccess($event)">
Β <adf-document-list
Β Β Β #documentList
Β Β Β [navigationMode]="'click'"
Β Β Β [currentFolderId]="getDocumentListCurrentFolderId()"
Β Β Β [contextMenuActions]="true"
Β Β Β [contentActions]="true">
Β Β Β <content-actions>
Β Β Β Β Β <!-- Folder actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'folder'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β (execute)="onDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'folder'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <!-- File actions -->
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DETAILS' | translate}}"
Β Β Β Β Β Β Β [icon]="'insert_drive_file'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β (execute)="onDetails($event)">
Β Β Β Β Β </content-action>
Β Β Β Β Β <content-action
Β Β Β Β Β Β Β title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
Β Β Β Β Β Β Β [icon]="'delete'"
Β Β Β Β Β Β Β [target]="'document'"
Β Β Β Β Β Β Β [permission]="'delete'"
Β Β Β Β Β Β Β [disableWithNoPermission]="true"
Β Β Β Β Β Β Β [handler]="'delete'"
Β Β Β Β Β Β Β (permissionEvent)="onContentActionPermissionError($event)"
Β Β Β Β Β Β Β (error)="onContentActionError($event)"
Β Β Β Β Β Β Β (success)="onContentActionSuccess($event)">
Β Β Β Β Β </content-action>
Β Β Β </content-actions>
Β </adf-document-list>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Β <adf-pagination
Β Β Β [target]="documentList">
Β </adf-pagination>
</adf-upload-drag-area>βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββWhat we have done here is copy the My Files list template and then added an extra toolbar at the top with theΒ <adf-sites-dropdownΒ ADF controlΒ drop down. Note that in contrast to My Files we do not customize the data-table column layout.Β For the site list drop down to work we need to implement theΒ getSiteContentΒ function that should return what content (i.e. folder) that should be displayed for the site when it is selected.
We will also hide the toolbar with the Upload files and Create Folder buttons if we are listing just the sites. This is checked by the siteListing() function.
That should do it as far as the page template goes. We need a bit of stylingΒ around the adf-sites-dropdown so add the app-site-container-style CSS class as follows in theΒ src/app/sites/sites-list-page/sites-list-page.component.scss file:
.app-site-container-style {
Β margin-top: 10px;
Β margin-bottom: 10px;
Β width: 100%;
Β min-width: 250px;
}
.adf-site-dropdown-container {
Β width: 400px;
Β .mat-form-field {
Β Β Β display: block;
Β Β Β margin-bottom: 15px;
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββNote how we also override one of the ADF CSS classes called adf-site-dropdown-container to be able to set the width. For this to work we need to set componentΒ encapsulation to ViewEncapsulation.None. See the component class implementation next.
Β
Now we need to implement the backing component class. We donβt want to implement everything from scratch like we did for the Repository List page component. Instead we want to reuse as much as possible. Open up the src/app/sites/sites-list-page/sites-list-page.component.tsΒ file and update it to look as follows:
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material';
import { SiteModel, NotificationService, ContentService, AlfrescoApiService } from '@alfresco/adf-core';
import { RepositoryListPageComponent } from '../../repository/repository-list-page/repository-list-page.component';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
@Component({
Β selector: 'app-sites-list-page',
Β templateUrl: './sites-list-page.component.html',
Β styleUrls: ['./sites-list-page.component.scss'],
Β encapsulation: ViewEncapsulation.None
})
export class SitesListPageComponent extends RepositoryListPageComponent implements OnInit {
Β sitesDataStore = '-sites-'; // By default display /Company Home/Sites
Β currentFolderId = this.sitesDataStore;
Β constructor(notificationService: NotificationService,
Β Β Β Β Β Β Β Β Β Β Β Β Β contentService: ContentService,
Β Β Β Β Β Β Β Β Β Β Β Β Β dialog: MatDialog,
Β Β Β Β Β Β Β Β Β Β Β Β Β activatedRoute: ActivatedRoute,
Β Β Β Β Β Β Β Β Β Β Β Β Β router: Router,
Β Β Β Β Β Β Β Β Β Β Β Β Β private apiService: AlfrescoApiService) {
Β Β Β super(notificationService, contentService, dialog , activatedRoute, router);
Β }
Β ngOnInit() {
Β Β Β super.ngOnInit();
Β }
Β siteListing() {
Β Β Β return this.currentFolderId === this.sitesDataStore;
Β }
Β getSiteContent(site: SiteModel) {
Β Β Β if (site && site.guid) {
Β Β Β Β Β this.getDocumentLibrarySiteContainer(site);
Β Β Β } else {
Β Β Β Β Β this.currentFolderId = this.sitesDataStore;
Β Β Β }
Β }
Β onDetails(event: any) {
Β Β Β const entry: MinimalNodeEntryEntity = event.value.entry;
Β Β Β this.router.navigate(['/sites', entry.id]);
Β }
Β private getDocumentLibrarySiteContainer(site: SiteModel): any {
Β Β Β const include = ['properties'];
Β Β Β const documentLibraryContainerId = 'documentLibrary';
Β Β Β this.apiService.getInstance().core.sitesApi.getSiteContainer(
Β Β Β Β Β site.id, documentLibraryContainerId, include).then((nodeEntity: MinimalNodeEntity) => {
Β Β Β Β Β const node: MinimalNodeEntryEntity = nodeEntity.entry;
Β Β Β Β Β this.currentFolderId = node.id;
Β Β Β });
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThe main thing to note here is that we are extending and reusing the functionality we implemented for the Repository List page component. As usual when extending another class we need to make sure the correct constructor is called with super(). We also make sure to call the superΒ ngOnInitΒ function.
Then we do the fundamental change that will make the document list display theΒ /Company Home/SitesΒ folder by default. We set theΒ currentFolderIdΒ to the pre configured data storeΒ -sites-. This also customises the document list view when you are just looking at a list of sites.Β Only the Name and Status columns are visible, the toolbar buttons are disabled, and there are no breadcrumbs. When you select a site from theΒ Site ListΒ drop down the Document Library content is displayed in the default data table layout with the standard columns Name, Size, Modified, and Modified By.
This is achieved by theΒ getSiteContentΒ function implementation:
getSiteContent(site: SiteModel) {
Β if (site && site.guid) {
Β Β Β this.getDocumentLibrarySiteContainer(site);
Β } else {
Β Β Β this.currentFolderId = this.sitesDataStore;
Β }
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΒ
It calls a method calledΒ getDocumentLibrarySiteContainer:
private getDocumentLibrarySiteContainer(site: SiteModel): any {
Β const include = ['properties'];
Β const documentLibraryContainerId = 'documentLibrary';
Β this.apiService.getInstance().core.sitesApi.getSiteContainer(
Β Β Β site.id, documentLibraryContainerId, include).then((nodeEntity: MinimalNodeEntity) => {
Β Β Β const node: MinimalNodeEntryEntity = nodeEntity.entry;
Β Β Β this.currentFolderId = node.id;
Β });
}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββThis function uses theΒ sitesApiΒ to get theΒ documentLibraryΒ container for a specific site id.
When the user clicks theΒ DetailsΒ content action for an item in the list we want the route to be fromΒ sitesΒ parent to the reused Repository Details page. So we override theΒ onFolderDetailsΒ andΒ onDocumentDetailsΒ functions. This way when you do go back from the details page you will end up again in the Site document list.
Thatβs pretty much it. The only thing left to do now is to import the components we are using, such as Document List, into the Sites module. Open up the src/app/sites/sites.module.tsΒ file and add the following:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SitesRoutingModule } from './sites-routing.module';
import { SitesPageComponent } from './sites-page/sites-page.component';
import { SitesListPageComponent } from './sites-list-page/sites-list-page.component';
import { AppCommonModule } from '../app-common/app-common.module';
@NgModule({
Β imports: [
Β Β Β CommonModule,
Β Β Β SitesRoutingModule,
Β Β Β /* Common App imports (Angular Core and Material, ADF Core, Content, and Process */
Β Β Β AppCommonModule
Β ],
Β declarations: [SitesPageComponent, SitesListPageComponent]
})
export class SitesModule { }βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββSummary
To work with versionΒ 2.0 of the Alfresco Application Development Framework (ADF)Β isΒ a very rewarding journey. The extensive range of ADF components that are available means that you can build a content management application from scratch in no time, picking the pieces you need.
My goal was to build a content management application where I could login and browse the repository, search the repository, upload files, view the details for folders and files, create new folders, edit metadata, and see a preview of the files. I was a bit sceptical in the beginning if it would be possible to do all that by just picking and choosing components, adding as I went along. But it really does work!
Next you might want to do the same on the APS side, check out theΒ Building a Process Management App with ADF 2.0.0Β article.