Skip navigation
All Places > Application Development Framework > Blog > 2017 > November
2017
ohej

Taking ADF 2.0 out for a spin

Posted by ohej Employee Nov 29, 2017

Now that ADF 2.0 is finally released, we need to update all our Getting Started content. While we're doing this, we wanted to give you a quick guide to create an ADF based application.

 

ADF 2.0 works with Alfresco Content Services 5.2.x, Alfresco Community Edition 5.2.x and Alfresco Process Services (Powered by Activiti) 1.6.4+/1.7.x. You'll need to have at least one of these running before going ahead. No special configuration is required during development.

 

Please note that ADF does not currently support Activiti, only Alfresco Process Services. We plan to bring this support in the future. Stay tuned for more news on this topic.

 

Since we're embracing Angular CLI, there are a few things you need to have installed:

 

Start by installing node.js. Once this is installed install Angular CLI, then Yeoman and the ADF App Generator:

$ npm install -g @angular/cli
$ npm install -g yo
$ npm install -g generator-alfresco-adf-app

Depending on your system, you may have to run the commands above with sudo.

 

Next up, let's generate our application with Yeoman:

$ yo alfresco-adf-app

 

The generator will now ask a few questions, mainly the name of your app, and which blueprint you want to use:

 

You can select one of three blueprints. To describe them from the bottom up:

Process Services

This will generate an application for Alfresco Process Services. It contains the following components: Login, App List, Task List, Form and Start Process.

 

Content Services

This will generate an application for Alfresco Content Services (or Alfresco Community Edition). It contains the following components: Login, Document List, Viewer.

 

Process and Content Services

This will generate an application for both Alfresco Process and Content Services and will be a combination of the two blueprints above.

 

Select the one you want to use, and the generator will now ask if you want to install dependencies right away. Enter "Y" and hit enter.

 

This can take a minute or two depending on your internet connection. You might see a few warnings at the end like this:

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN @angular/flex-layout@2.0.0-beta.10 requires a peer of @angular/core@0.0.0-NG but none is installed. You must install peer dependencies yourself.
npm WARN @angular/flex-layout@2.0.0-beta.10 requires a peer of @angular/common@0.0.0-NG but none is installed. You must install peer dependencies yourself.
npm WARN @angular/material-moment-adapter@5.0.0-rc0 requires a peer of moment@^2.18.1 but none is installed. You must install peer dependencies yourself.
npm WARN codelyzer@3.2.2 requires a peer of @angular/compiler@^2.3.1 || >=4.0.0-beta <5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN codelyzer@3.2.2 requires a peer of @angular/core@^2.3.1 || >=4.0.0-beta <5.0.0 but none is installed. You must install peer dependencies yourself.

 

These warnings are normal. Unfortunately they happen within the Angular Flex Layout package that ADF depends on. You can safely ignore them. 

 

Next you want to change into the directory of your app and inspect the proxy.conf.json file. This file will tell Webpack to create a proxy for your backend (Content or Process services). Change the URLs and ports to reflect where they are currently running. In my case I have ACS running on http://localhost:8080 and APS running on http://localhost:9999, so my proxy.conf.json file should look like this:

{
  "/alfresco": {
    "target": "http://localhost:8080",
    "secure": false,
    "changeOrigin": true
  },
  "/activiti-app": {
    "target": "http://localhost:9999",
    "secure": false,
    "changeOrigin": true
  }
}

 

You'll notice that this is an Angular CLI project, so you can now take advantage of all the CLI commands to generate new components, modules and much more. We encourage you to checkout the Angular CLI documentation.

 

Now you can simply run "npm start". This takes a couple of seconds, and will automatically open a browser to http://localhost:4200/. Once the page loads, you can navigate to http://localhost:4200/login which should look like this:

 

Enter a valid username and password and you have now successfully created your very first ADF 2.0 application!

 

Happy hacking!

ohej

Announcing ADF 2.0 GA

Posted by ohej Employee Nov 29, 2017

We're pleased to announce that the Application Development Framework 2.0 has now been released!

 

While this release had a high focus on stabilization and bug fixes, we have also introduced new and enhanced features. These include a completely revamped Viewer component, a new metadata component and file versioning. We have updated to Angular 5 and Angular Material. Also, ADF is now localized into 11 different languages. In total, we have closed around 242 issues in JIRA. 

 

Please see the ADF 2.0.0 Release Note for all the juicy details!

 

Since we follow SEMVER, we can only introduce breaking changes in major releases. We started ADF in April 2016 and we have learned a lot since then. We have taken this opportunity to restructure our packages, refactor components and keep up with the developments from the Angular community. Unfortunately, this means that we have done some breaking changes which we blogged about a few weeks ago in Preparing for ADF 2.0. You can also find more details in the release notes, and we will be providing guidance on how to upgrade existing projects to 2.0.

 

With ADF 2.0 we have embraced Angular CLI for developing ADF applications, which means you can now leverage this tool during development to help scaffold components, modules and much more. We have updated our Yeoman generators to reflect this, so it will now give you three choices, either create a Process + Content app, Process app or a Content app. The end result is a basic ADF based application that gives you a great starting point. 

 

We have continued to improve on the documentation and we can now officially share the new site for ADF's documentation: ADF Component Catalog

 

We are currently updating all our "Getting Started" material to reflect the latest and greatest, but here are some quick instructions to get the latest and greatest running: Taking ADF 2.0 out for a spin.

 

As part of the changes for 2.0, we will also be renaming the Github repositories for ADF, however, we couldn't get this done prior to the release so this will happen shortly. Luckily, Github will automatically set up redirects so we don't expect any issues regarding this. 

 

ADF 2.0 is the first General Available release of ADF. This means that Enterprise customers can now get support via the support portal, and customers with Developer Support will have you covered.

 

The Alfresco University team has been hard at work developing training content. We will be releasing two new courses, with the first of these available on the release date: 

Alfresco Application Development Framework Overview
This eLearning course is designed for those wishing to learn about the new Application Development Framework (ADF). Through a series of lectures, videos, and demonstrations, students will walk away with a thorough understanding of the ADF platform.

Shortly after ADF 2.0 is released the second course will be available:
Creating and Customizing an Application using Alfresco ADF
This eLearning course is designed for those wishing to learn more about the Application Development Framework (ADF). Using a scenario-based approach and through a series of lectures, videos, demonstrations, and lab exercises, students will learn how to create and customize an ADF application.

 

As always we welcome feedback! Join the Gitter channel where the entire team hangs out, raise an issue or pull request on Github, or leave a comment below.

ohej

Preparing for ADF 2.0

Posted by ohej Employee Nov 28, 2017

We've been busy in the last months preparing for the next major release of ADF. Since we follow SEMVER we can only introduce breaking changes in major versions, and we're currently at a point where we not only flip the version number to 2.0 but we also go into General Availability, meaning ADF will be fully supported by Alfresco.

 

We are introducing a set of new features, most notably the File Viewer component. The viewer was one of the first components to be created but in 2.0 we have given it a complete rework. It has a new design, along with a lot of ways to customize, including the info drawer, toolbar and thumbnails. We will provide a more in-depth write up of the new features in the release notes.

 

To support the new File Viewer, we have added a new component based on the Info Drawer to show metadata from Content Services. This component can be externally configured to include/exclude metadata and offers inline editing of properties as well.

 

The Yeoman Generators have been given a lot of love. We are embracing Angular CLI, and in 2.0 the Yeoman Generators will give you three options: Create a Process + Content app, a Content app or a Process app. These apps will have a very small footprint and have the basic setup you need to get up and running with ADF and Angular.

 

Last but not least, we have made big improvements to the documentation. Although we have added some completely new docs, we have mainly focused on gathering the information that was already there and making it easier to find and use. On GitHub, we now have one Markdown file per component and an index page that gives clearer detail about what is available in the docs. Internally, the Markdown files now have a more consistent structure that should make them easier to read, write and maintain. Also, we have added more links between the doc pages and to outside resources (community, other APIs, etc). This should help users form a better picture of how ADF fits together and of its relationship to the rest of the Node/Angular ecosystem.

 

This is just a small subset of the changes - we will be detailing everything in the release notes.

 

ADF has come a long way since its beginning. We have learned a lot and are adjusting based on feedback from our customers, partners and community. Some of these adjustments will include breaking changes, and we would like to take this opportunity to give some early insight on what to expect when upgrading to ADF 2.0.

 

Renaming and refactoring
When we started with ADF we used an early release of Angular2. Back then it was common practise to name components with a ng2-* prefix. Back in May we upgraded to Angular4 and last week we merged the pull request to upgrade to Angular5. Because of this we want to remove the ng2-* prefix and instead just be ADF.

 

We also used the old naming conventions from Alfresco, so all components were either ng2-alfresco-* or ng2-activiti-*.

As a result of this we are doing some house keeping in the Github repository and renaming all ADF components.

From ADF 2.0 we will split everything into three main packages:

@alfresco/adf-content-service
@alfresco/adf-process-services
@alfresco/adf-core
@alfresco/adf-insights


The Alfresco Javascript API remains the same and will be deployed as previously.
The structure of the Github repository will be re-arranged to reflect these changes.

The impact for developers using ADF is that the dependencies need to be adjusted (and simplified) in package.json, and all import statements in typescript files need to be changed.


A concrete example of this would be

import { FormRenderingService, FormService, ValidateFormFieldEvent, FormEvent, FormComponent } from 'ng2-activiti-form';

This would need to be changed to

import { FormService, FormEvent, FormComponent } from '@alfresco/adf-core';


Updating to the latest Angular Material Design
Recently we completed the transition from Material Design Lite to Angular Material Design which is more complete and has more features.


The Angular Material Design project recently introduced a number of breaking changes as they are moving towards a major release as well.

 

In the 2.0.0-beta11 release of Angular Material Design the team decided to rename all md-* prefixes to mat-*. The team have provided a tool to help migrating. To use this tool, please make sure you run it before upgrading to ADF 2.0. As of this writing we are currently on the 5.0.0-rc0 release of Angular Material Design.

 

This is mostly an exercise of search'n'replace in the source, along with some testing to ensure you have caught everything.

We highly recommend checking out the breaking changes for the last couple of releases here: https://github.com/angular/material2/releases

 

Renaming events
Following the guidance from the Angular project, we have renamed all our components' events so they no longer start with "on", which impacts all use of events from the ADF components. Examples of this would be "(onSuccess)" is now "(success)", "(onSave)" is just "(save)".

 

With these changes in mind, we strongly believe we have the right structure and naming to grow and scale ADF. We expect to release ADF 2.0 by the end of November.

Introduction

In this article we will look at how you can use the Alfresco Process Services (APS) related ADF components to build a Process 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.

 

There is a newer version of this article covering ADF 2.0: Building a Process Management App with ADF 2.0.0

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 1.9.0 and the second article improves on the first article by adding a navigation system, menu, toolbar, and logout functionality.

 

We want our new process management application interface to look something like this:

 

This application should allow the user to browse the Process Applications available in APS. While browsing the Apps the user will be able to look at the associated Process Definitions, and start processes based on those definitions. It will then be possible to interact with the active Task Instances. Running Process Instances can also be inspected directly. The application will cover most of of the available APS related ADF components.

 

It is likely that you will create different types of Process Management applications depending on the user role. And you will probably not use all the ADF components shown in this article in every application. For example, if the user should only interact with ongoing Task Instances assigned to them, then you would use ADF components shown in the My Tasks section. If the user should be able to start Process Instances, then you would look at ADF components talked about in the Process Apps section. If the user is a sys admin, then they would most likely benefit from the ADF components used in the My Processes section.

 

Before we start building an application with the Process Services related ADF components it might be a good idea to know what they actually are. At first you might think, great, now I can build a custom UI for all the workflows I got deployed in my Alfresco Content Services (ACS) installation. As you know, ACS has the Activiti workflow engine embedded, and most consulting projects revolving around Alfresco has traditionally implemented business processes with this workflow engine. The Process Services ADF components cannot be used for ACS workflows. They can only be used for Alfresco’s stand-alone Business Process Management solution, which is Alfresco Process Services (APS).

 

The Process Services ADF components uses indirectly, via the Alfresco JavaScript API,  the APS ReST API, which is separate from the ACS ReST API.

 

The Process Services ADF components can also not currently be used to design and implement workflows. That still need to be done via the APS App Designer.

 

Prerequisites

This application tutorial requires an Alfresco Process Services (APS) 1.6.4 installation and a 'starter app'.

Installing APS 1.6.4

There are several Alfresco ADF version 1.9.0 components that does not work with APS 1.7.0. So you are going to need to run APS 1.6.4 when walking through this article. If you are not already running APS 1.6.4, then the easiest (and maybe only) way to get it is as a Docker Image.

 

If you don't have Docker CE, download and follow official installation guides here.  

 

When you got Docker installed it is easy to get up and running with APS 1.6.4:

 

$ docker container run -d -p 9080:8080 --name aps164 alfresco/process-services:1.6.4

 

This runs a new Docker container with the name aps164 containing APS version 1.6.4 that you can access on http://localhost:9080/activiti-app. The starter app that you will download in the next section is preconfigured to expect APS to be running on port 9080. If you want to use a different port change the port number in the starter app file proxy.conf.json.

 

APS requires a license and when you first access it there will be a red information box about uploading a license. You can actually go through and request a trial license as described in my first article in this series, and then upload this license. It should work also with version 1.6.4 even though the trial is now at 1.7.0.

If you want to know what APS Docker Images that are available, then have a look here

Getting the Starter App

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-nav.git adf-workbench-process

 

This clones the starter project within a new directory called adf-workbench-process. Install all the packages for the project like this:

 

Martins-Macbook-Pro:ADF mbergljung$ cd adf-workbench-process

Martins-Macbook-Pro:adf-workbench-process mbergljungnpm install

Martins-Macbook-Pro:adf-workbench-process mbergljungnpm 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 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-process.git adf-workbench-process-src

Listing Process Apps and Process Definitions

Now that we have the Angular app ready for ADF, and we can login to APS, it’s time to add some Process Services specific ADF components. Let’s start by building a Process Application view. Why do we do this? It is because when you work with processes in Alfresco Process Services (APS) they are all contained within applications.  You usually start by creating a Process App and then you define one or more process definitions and add them to it.

Generating the Process Apps module and page components

We are going to need a Process Apps parent page component. It will contain two child page components, one for the Process Apps List and one for the Process Apps Details page 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:

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ ng g module process-apps --flat false --routing

  create src/app/process-apps/process-apps-routing.module.ts (254 bytes)

  create src/app/process-apps/process-apps.module.ts (300 bytes)

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ cd src/app/process-apps/

 

Martins-MacBook-Pro:process-apps mbergljung$ ng g component process-apps-page

  create src/app/process-apps/process-apps-page/process-apps-page.component.css (0 bytes)

  create src/app/process-apps/process-apps-page/process-apps-page.component.html (36 bytes)

  create src/app/process-apps/process-apps-page/process-apps-page.component.spec.ts (693 bytes)

  create src/app/process-apps/process-apps-page/process-apps-page.component.ts (311 bytes)

  update src/app/process-apps/process-apps.module.ts (416 bytes)

 

Martins-MacBook-Pro:process-apps mbergljung$ ng g component process-apps-list-page

  create src/app/process-apps/process-apps-list-page/process-apps-list-page.component.css (0 bytes)

  create src/app/process-apps/process-apps-list-page/process-apps-list-page.component.html (41 bytes)

  create src/app/process-apps/process-apps-list-page/process-apps-list-page.component.spec.ts (722 bytes)

  create src/app/process-apps/process-apps-list-page/process-apps-list-page.component.ts (330 bytes)

  update src/app/process-apps/process-apps.module.ts (552 bytes)

 

Martins-MacBook-Pro:process-apps mbergljung$ ng g component process-apps-details-page

  create src/app/process-apps/process-apps-details-page/process-apps-details-page.component.css (0 bytes)

  create src/app/process-apps/process-apps-details-page/process-apps-details-page.component.html (44 bytes)

  create src/app/process-apps/process-apps-details-page/process-apps-details-page.component.spec.ts (743 bytes)

  create src/app/process-apps/process-apps-details-page/process-apps-details-page.component.ts (342 bytes)

  update src/app/process-apps/process-apps.module.ts (700 bytes)

 

This creates a Process Apps module with routing, a Process Apps parent page, and a Process Apps list page component where we will display the available process applications. It also creates a Process Apps details page where we can display more detailed information for a specific process application.

 

We need to set up a new route configuration with a parent route that has two child routes for the list page and details page. Let’s add it in the src/app/process-apps/process-apps-routing.module.ts file as follows:

 

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ProcessAppsPageComponent } from './process-apps-page/process-apps-page.component';
import { ProcessAppsListPageComponent } from './process-apps-list-page/process-apps-list-page.component';
import { ProcessAppsDetailsPageComponent } from './process-apps-details-page/process-apps-details-page.component';

import { AuthGuardBpm } from 'ng2-alfresco-core';

const routes: Routes = [ {
  path: 'process-apps',
  component: ProcessAppsPageComponent,
  canActivate: [AuthGuardBpm],
  data: {
    title: 'Process Apps',
    icon: 'apps',
    hidden: false,
    needBpmAuth: true,
    isLogin: false
  },
  children: [
    { path: '', component: ProcessAppsListPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id', component: ProcessAppsDetailsPageComponent, canActivate: [AuthGuardBpm] }
  ]
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProcessAppsRoutingModule { }

 

When we use the http://localhost:4200/process-apps URL we will hit the parent page component ProcessAppsPageComponent and as there is a child component ProcessAppsListPageComponent

 with empty path '' it will automatically be invoked and the process apps list displayed.

 

When one of the items in the process apps list is clicked, and we select a Details content action from the 'Three Dots' menu, then the http://localhost:4200/process-apps/<process-app-id> URL will be invoked taking the user to the ProcessAppsDetailsPageComponent.

 

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 AuthGuardBpm ADF component makes sure that the route cannot be activated if the user is not authenticated with APS. Which leads us to make sure that the app is set up to authenticate with APS 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]="'BPM'"
    [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 { ProcessAppsModule } from './process-apps/process-apps.module';
import { ProcessAppsRoutingModule } from './process-apps/process-apps-routing.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,

    AppCommonModule,
    AppLoginModule,
    AppLoginRoutingModule,
    ProcessAppsModule,
    ProcessAppsRoutingModule
  ],
  providers: [AppMenuService],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

Automatically route to the Process Apps List page after Login

First, start up the server as follows:

Martins-MacBook-Pro:adf-workbench-process mbergljung$ npm start

 

Then access the app at http://localhost:4200 and login to APS:

 

 

You should see the Process Apps menu item in the left Navigation Menu. Let's configure the login component to route to the Process Apps parent page directly after a successful login. Open up the src/app/app-login/app-login-page/app-login-page.component.ts file and uncomment the router code in the onLoginSuccess method and have it navigate to /process-apps:

 

  onLoginSuccess($event) {
    console.log('Successful login: ' + $event.value);

    // Tell parent component that successful login has happened and menu should change
    this.menuService.fireMenuChanged();

    // Now, navigate somewhere...
    this.router.navigate(['/process-apps']);
  }

 

If you now logout and then login again, you will see that the Process Apps parent page is displayed automatically after a successful login:

 

However, what we really want is for the Process Apps List page to be displayed automatically after a successful login. This can be fixed by putting a router-outlet in the Process Apps parent page template. Open up the src/app/process-apps/process-apps-page/process-apps-page.component.html file and add it as follows:

 

<router-outlet></router-outlet>

 

If you now again logout and then login, you will see that the Process Apps List page is displayed automatically after a successful login:

 

 

Implementing the Process Apps List page

Now we can move on and implement the Process Apps List component that will provide a list of the applications that the logged in user has access to. To do this we use ADF components in the ng2-activiti-tasklist package. Install it as follows:

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ npm install --save-exact ng2-activiti-tasklist@1.9.0

+ ng2-activiti-tasklist@1.9.0

 

This package indirectly depends on the ng2-activiti-form and the ng2-alfresco-datatable, so they are fetched as well in the background, but not added to package.json. There are assets/resources that we need to bring in so Webpack includes them in the app. Do this in .angular-cli.json:

 

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "adf-workbench"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico",
        { "glob": "**/*", "input": "../node_modules/ng2-alfresco-core/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-alfresco-login/bundles/assets", "output": "./assets/" },
        { "glob": "**/*", "input": "../node_modules/ng2-activiti-tasklist/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-activiti-form/bundles/assets", "output": "./assets/" },
        { "glob": "**/*", "input": "../node_modules/ng2-alfresco-datatable/bundles/assets", "output": "./assets/" }
      ],

 

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.

Let’s start with the page template for the Process App list. Open up the src/app/process-apps/process-apps-list-page/process-apps-list-page.component.html file and replace whatever is there with the following:

 

<div class="process-apps-list-height">
  <adf-apps
    [layoutType]="'LIST'"
    [filtersAppId]="processAppsFilter"
    (appClick)="onAppClick($event)">

  </adf-apps>
</div>

 

Here we are configuring the page view to display a list of Process Applications with the ADF component <adf-apps. We enclose the Process Apps list in a div with a class that sets the height of the page so the left navigation menu will be visible even if there are no apps available, or just one. Add the CSS class to the src/app/process-apps/process-apps-list-page/process-apps-list-page.component.css file as follows:

 

.process-apps-list-height {
  height:900px;
}

 

The <adf-apps component is not yet known to the ProcessAppsModule, so your editor might scream about it. Import it as follows in the src/app/process-apps/process-apps.module.ts file:

 

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProcessAppsRoutingModule } from './process-apps-routing.module';
import { ProcessAppsPageComponent } from './process-apps-page/process-apps-page.component';
import { ProcessAppsListPageComponent } from './process-apps-list-page/process-apps-list-page.component';
import { ProcessAppsDetailsPageComponent } from './process-apps-details-page/process-apps-details-page.component';

import { AppCommonModule } from '../app-common/app-common.module';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';

import { CardViewUpdateService } from 'ng2-alfresco-core';

@NgModule({
  imports: [
    CommonModule,
    ProcessAppsRoutingModule,

    /* Common App imports (Angular Core and Material, ADF Core */
    AppCommonModule,

    /* ADF libs specific to this module */
    ActivitiTaskListModule
  ],
  declarations: [ProcessAppsPageComponent, ProcessAppsListPageComponent, ProcessAppsDetailsPageComponent],
  providers: [CardViewUpdateService] /* Need to set it up as a provider here as there is a bug in CoreModule, it does not import... */
})
export class ProcessAppsModule { }

 

 

Here we import the ActivitiTaskListModule, which gives us access to the <adf-apps component. At the same time we also import the AppCommonModule that comes with the project we cloned, it provides all the Angular Material components and the ADF Core component. And we also specify provider CardViewUpdateService, which will be needed later on when implementing the process app details page (it is available in ADF Core).

 

We are configuring the process app list layoutType to LIST so we will see a view such as in this example:



Note that you will see other apps in your list depending on what you have previously created in APS. Or you will just see the Task App if you have not yet created any Process Apps.

It is also possible to use the more familiar, at least if you have used APS for a bit, grid view. Setting [layoutType]="'GRID'" will display as:



 

Choose whichever layout type you like, I will stick with LIST in this article.

 

You can control what process apps that are displayed via the filtersAppId property. In this case it is set to class variable processAppsFilter, add this variable to the  class as follows:

 

export class ProcessAppsListPageComponent implements OnInit {
  processAppsFilter = [ { tenantId: 1 } ];

  constructor() { }

  ngOnInit() {
  }
}

 

This means that a Process App needs to be associated with tenant 1 to be included. When we skip this filter we also see the out-of-the-box Task AppWe don’t want the Task App included as it isn't a Process App in the sense that it contains Process Definitions. And we are also going to build our own “My Tasks” view where we can take part in active workflows and complete tasks. 

 

The last configuration for the Process App list is an event to capture when an app in the list is clicked. The onAppClick method is then called so we can navigate to the process app details page. Open up the src/app/process-apps/process-apps-list-page/process-apps-list-page.component.ts file and update it as follows:

 

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { AppDefinitionRepresentationModel } from 'ng2-activiti-tasklist';

@Component({
  selector: 'app-process-apps-list-page',
  templateUrl: './process-apps-list-page.component.html',
  styleUrls: ['./process-apps-list-page.component.css']
})
export class ProcessAppsListPageComponent implements OnInit {
  /* Create a filter that will exclude the Task List App and include only new custom apps */
  processAppsFilter = [ { tenantId: 1 } ];

  constructor(private router: Router) { }

  ngOnInit() {
  }

  onAppClick(appDef: AppDefinitionRepresentationModel) {
    console.log('Navigating to App Definition : ', appDef);

    this.router.navigate(['/process-apps', appDef.id]);
  }
}

 

When an app is clicked in the list the event handler method is passed an object of type AppDefinitionRepresentationModel. It looks like this:

 

export class AppDefinitionRepresentationModel {
  defaultAppId: string;
  deploymentId: string;
  name: string;
  description: string;
  theme: string;
  icon: string;
  id: number;
  modelId: number;
  tenantId: number;

 

So we can see that we already at this point know a lot of information about the process app. Although we only need the id to pass on as URL parameter when we navigate to the Process App Details page.

  

This finishes off the Process App list page. Let’s move on with the details page.

Implementing the Process App Detail page

When the user clicks on one of the Process Apps a detail page such as the following should be displayed:

 

 

The page will have an orange toolbar with two buttons, one for displaying the process definitions that are associated with the app, and one for closing the details view. It will then display the process app properties in an ADF Card View.

 

Let’s start with the page template, open up the src/app/process-apps/process-apps-details-page/process-apps-details-page.component.html file and update it so it looks like this:

 

<adf-toolbar [color]="'accent'">
  <adf-toolbar-title>
      <span *ngIf="appName">{{ appName }}</span>
  </adf-toolbar-title>
  <button md-icon-button
          mdTooltip="Show Process Definitions included in app"
          (click)="onShowProcDefs($event)">

    <md-icon>device_hub</md-icon>
  </button>
  <adf-toolbar-divider></adf-toolbar-divider>
  <button md-icon-button
          class="adf-viewer-close-button"
          mdTooltip="Close and go back to Process App list"
          (click)="onGoBack($event)"
          aria-label="Close">

    <md-icon>close</md-icon>
  </button>
</adf-toolbar>
<md-card class="adf-card-container">
  <md-card-content>
    <adf-card-view
      [properties]="properties"
      [editable]="false">
 
    </adf-card-view>
  </md-card-content>
</md-card>

 

We start by using the ADF specific toolbar (<adf-toolbar), which will have a title and two buttons. One button to navigate to the associated Process Definitions and one two navigate back to the Process App list. The md-icon value is as usual taken from the list of available Material Design icons.

 

We then use some of the Angular Material components (i.e. all tags starting with md-) to build the Process App Details view. These components are already imported via the AppCommonModule. To present the Process App Details we use the ADF Card View component (<adf-card-view), which is part of the ng2-alfresco-core package. The Card view can be used to present a list of properties in an easy way.

If you need a more sophisticated approach for your properties layout, then have a look at the ADF Form component. It is described in the Content Management App article.

The backing component class is implemented in the src/app/process-apps/process-apps-details-page/process-apps-details-page.component.ts file and looks like this:

 

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { CardViewItem, CardViewTextItemModel } from 'ng2-alfresco-core';

import { AppDefinitionRepresentationModel, TaskListService } from 'ng2-activiti-tasklist';

@Component({
  selector: 'app-process-apps-details-page',
  templateUrl: './process-apps-details-page.component.html',
  styleUrls: ['./process-apps-details-page.component.css']
})
export class ProcessAppsDetailsPageComponent implements OnInit {
  appId: number;
  appName: string;
  properties: Array<CardViewItem>;

  constructor(private router: Router,
              private activatedRoute: ActivatedRoute,
              private taskListService: TaskListService) {
    this.properties = new Array<CardViewItem>();
  }

  ngOnInit() {
    this.appId = +this.activatedRoute.snapshot.params['process-app-id'];
    console.log('Showing app details for: ', this.appId);
    this.taskListService.getApplicationDetailsById(this.appId).subscribe(
      (appDef: any) => {
        console.log('App details: ', appDef);
        this.appName = appDef.name;
        this.setupProps(appDef);
      },
      (error) => {
        console.log('Error: ', error);
      });
  }

  private setupProps(appDef: AppDefinitionRepresentationModel) {
    console.log('setupProps: ', appDef.id);

    // Properties that are always available
    const idProp = new CardViewTextItemModel({label: 'Id:', value: appDef.id, key: 'appId'});
    const defaultAppIidProp = new CardViewTextItemModel({label: 'Default App Id:', value: appDef.defaultAppId, key: 'defaultAppId'});
    const tenantIdProp = new CardViewTextItemModel({label: 'Tenant Id:', value: appDef.tenantId, key: 'tenantId'});
    const deploymentIdProp = new CardViewTextItemModel({label: 'Deployment Id:', value: appDef.deploymentId, key: 'deploymentId'});
    const modelIdProp = new CardViewTextItemModel({label: 'Model Id:', value: appDef.modelId, key: 'modelId'});
    const nameProp = new CardViewTextItemModel({label: 'Name:', value: appDef.name, key: 'appName'});
    const descProp = new CardViewTextItemModel({label: 'Description:', value: appDef.description, key: 'appDesc'});
    const iconProp = new CardViewTextItemModel({label: 'Icon:', value: appDef.icon, key: 'appIcon'});

    this.properties.push(idProp);
    this.properties.push(defaultAppIidProp);
    this.properties.push(tenantIdProp);
    this.properties.push(deploymentIdProp);
    this.properties.push(modelIdProp);
    this.properties.push(nameProp);
    this.properties.push(descProp);
    this.properties.push(iconProp);
  }

  onGoBack($event: Event) {
    this.navigateBack2AppList();
  }

  private navigateBack2AppList() {
    this.router.navigate(['../'],
      {
        relativeTo: this.activatedRoute
      });
  }

  onShowProcDefs($event: Event) {

  }
}

 

The component class defines the appId, appName, and properties variables that are referred to in the template. The appId is passed in via the URL, which looks like http://localhost:4200/process-apps/<appId>. We extract the appId in the ngOnInit method via the activedRoute object. Important to note here is that the getApplicationDetailsById function expects the type of the appId to be a number. So we need to make sure it is a number and do this.appId = +this.activatedRoute.snapshot.params.

 

When we have the ID we use the ADF TaskListService to fetch an AppDefinitionRepresentationModel object representing the info about the Process App. We then use this object to set up the properties that backs 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 implement the navigate back handler by just stepping up to the parent route. We don’t need to pass anything back to the Process App list as it always just displays all apps.

 

The onShowProcDefs is left empty at the moment and will be implemented in the next section.

 

That should do it, you can now try out the Process App list and details pages. Remember, we changed to .angular-cli.json file, so we need to restart the server.

Displaying Process Definitions for a Process App

Each Process App can have many different Process Definitions associated with it. So when a user is given access to the Process App they can start process instances based on any of these Process Definitions.

 

To display Process Definitions for an app we are going to need a Process Definitions List page, it will look something like this:

 

 

The user will be able to click on the Three Dots menu to the right to navigate to a details page for the process definition:

 

 

The Process Definitions Details page will look like this, showing the Properties tab by default:

 

 

Clicking on the Preview tab displays a preview of the Process Definition diagram:



 

These components will all be part of the ProcessAppsModule.

Generating the Process Definitions page components

We are going to need two page components, one for the Process Definition List and one for the Process Definition Details page component. We don't need a new module as these pages will be child pages of the Process Apps parent page.

 

As usual, we can easily create the needed components with the Angular CLI tool:

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ cd src/app/process-apps/

 

Martins-MacBook-Pro:process-apps mbergljung$ ng g component process-definitions-list-page

  create src/app/process-apps/process-definitions-list-page/process-definitions-list-page.component.css (0 bytes)

  create src/app/process-apps/process-definitions-list-page/process-definitions-list-page.component.html (48 bytes)

  create src/app/process-apps/process-definitions-list-page/process-definitions-list-page.component.spec.ts (771 bytes)

  create src/app/process-apps/process-definitions-list-page/process-definitions-list-page.component.ts (358 bytes)

  update src/app/process-apps/process-apps.module.ts (1352 bytes)

 

Martins-MacBook-Pro:process-apps mbergljung$ ng g component process-definitions-details-page

  create src/app/process-apps/process-definitions-details-page/process-definitions-details-page.component.css (0 bytes)

  create src/app/process-apps/process-definitions-details-page/process-definitions-details-page.component.html (51 bytes)

  create src/app/process-apps/process-definitions-details-page/process-definitions-details-page.component.spec.ts (792 bytes)

  create src/app/process-apps/process-definitions-details-page/process-definitions-details-page.component.ts (370 bytes)

  update src/app/process-apps/process-apps.module.ts (1528 bytes)

 

This creates a Process Definitions list page component where we will display the available process definitions for the selected Process App. It also creates a Process Definitions details page where we can display more detailed information for a selected process definition.

 

We need to define two new route configurations for the list page and details page. Let’s add it in the src/app/process-apps/process-apps-routing.module.ts file as follows:

 

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ProcessAppsPageComponent } from './process-apps-page/process-apps-page.component';
import { ProcessAppsListPageComponent } from './process-apps-list-page/process-apps-list-page.component';
import { ProcessAppsDetailsPageComponent } from './process-apps-details-page/process-apps-details-page.component';
import { ProcessDefinitionsListPageComponent } from './process-definitions-list-page/process-definitions-list-page.component';
import { ProcessDefinitionsDetailsPageComponent } from './process-definitions-details-page/process-definitions-details-page.component';


import { AuthGuardBpm } from 'ng2-alfresco-core';

const routes: Routes = [ {
  path: 'process-apps',
  component: ProcessAppsPageComponent,
  canActivate: [AuthGuardBpm],
  data: {
    title: 'Process Apps',
    icon: 'apps',
    hidden: false,
    needBpmAuth: true,
    isLogin: false
  },
  children: [
    { path: '', component: ProcessAppsListPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id', component: ProcessAppsDetailsPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id/procdef-list', component: ProcessDefinitionsListPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id/procdef-details/:process-def-id', component: ProcessDefinitionsDetailsPageComponent, canActivate: [AuthGuardBpm] }
  ]
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProcessAppsRoutingModule { }

 

The route to get to the Process Definitions for a Process App is http://localhost:4200/process-apps/<process-app-id>/procdef-list. To get to the details page for a Process Definition we will use the http://localhost:4200/process-apps/<process-app-id>/procdef-details/<process-def-id>  URL. You can try these URLs right now if you like by typing them in manually. 

 

We configure the routes to these page components to be children of the process app parent component route. This means we can use the same router-outlet.

Implementing the Process Definitions List

Now we can move on and implement the Process Definitions List component that will provide a list of the Process Definitions that the current app has access to. To do this we use ADF services in the ng2-activiti-processlist package. Install it as follows:

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ npm install --save-exact ng2-activiti-processlist@1.9.0

+ ng2-activiti-processlist@1.9.0

 

There are assets/resources that we need to bring in so Webpack includes them in the app. Do this in .angular-cli.json:

 

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "adf-workbench"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico",
        { "glob": "**/*", "input": "../node_modules/ng2-alfresco-core/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-alfresco-login/bundles/assets", "output": "./assets/" },
        { "glob": "**/*", "input": "../node_modules/ng2-activiti-tasklist/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-activiti-form/bundles/assets", "output": "./assets/" },
        { "glob": "**/*", "input": "../node_modules/ng2-alfresco-datatable/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-activiti-processlist/bundles/assets", "output": "./assets/" }
      ],

 

The new ActivitiProcessListModule need to be made available to our ProcessAppsModule. Open up the src/app/process-apps/process-apps.module.ts file and add it:

 

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProcessAppsRoutingModule } from './process-apps-routing.module';
import { ProcessAppsPageComponent } from './process-apps-page/process-apps-page.component';
import { ProcessAppsListPageComponent } from './process-apps-list-page/process-apps-list-page.component';
import { ProcessAppsDetailsPageComponent } from './process-apps-details-page/process-apps-details-page.component';
import { ProcessDefinitionsListPageComponent } from './process-definitions-list-page/process-definitions-list-page.component';
import { ProcessDefinitionsDetailsPageComponent } from './process-definitions-details-page/process-definitions-details-page.component';

import { AppCommonModule } from '../app-common/app-common.module';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { ActivitiProcessListModule } from 'ng2-activiti-processlist';
import { DataTableModule } from 'ng2-alfresco-datatable';

import { CardViewUpdateService } from 'ng2-alfresco-core';

@NgModule({
  imports: [
    CommonModule,
    ProcessAppsRoutingModule,

    /* Common App imports (Angular Core and Material, ADF Core */
    AppCommonModule,

    /* ADF libs specific to this module */
    ActivitiTaskListModule,
    ActivitiProcessListModule,
    DataTableModule
  ],
  declarations: [ProcessAppsPageComponent, ProcessAppsListPageComponent, ProcessAppsDetailsPageComponent,
    ProcessDefinitionsListPageComponent, ProcessDefinitionsDetailsPageComponent],
  providers: [CardViewUpdateService] /* Need to set it up as a provider here as there is a bug in CoreModule, it does not import... */
})
export class ProcessAppsModule { }

 

There’s no ADF Process Definition List component, so we have to implement the process definition list view ourselves. We will do this with an ADF Data Table component, so we take the opportunity to include the DataTableModule as well. This means that we will get familiar with how to working with the ADF Data Table component, which is good.

 

Let’s start with the component template for the process definition list. Open up the src/app/process-apps/process-definitions-list-page/process-definitions-list-page.component.html file and replace what is there with this:

 

<adf-toolbar [color]="'accent'">
  <adf-toolbar-title>
      <span *ngIf="appName">Process Definitions associated with app '{{ appName }}'</span>
  </adf-toolbar-title>
  <adf-toolbar-divider></adf-toolbar-divider>
  <button md-icon-button
                  class="adf-viewer-close-button"
                  mdTooltip="Close and go back to process app list"
                  (click)="onGoBack($event)"
                  aria-label="Close">

      <md-icon>close</md-icon>
  </button>
</adf-toolbar>
<adf-datatable
  [data]="data"
  [loading]="isLoading"
  [actions]="true"
  [actionsPosition]="'right'"
  [contextMenu]="true"
  [showHeader]="true"
  (showRowActionsMenu)="onShowThreeDotsMenu($event)"
  (executeRowAction)="onExecuteThreeDotsMenuItem($event)"
  (showRowContextMenu)="onShowDropDownMenu($event)"
  (rowClick)="onRowClick($event)">

  <data-columns>
      <data-column title="Id" key="id" ></data-column>
      <data-column title="Name" key="name" class="full-width name-column"></data-column>
      <data-column title="Key" key="key" ></data-column>
      <data-column title="Version" key="version"></data-column>
  </data-columns>
  <loading-content-template>
      <!-- Show a spinner if loading is going on -->
      <ng-template>
        <md-progress-spinner
          class="adf-process-list-loading-margin"
          [color]="'primary'"
          [mode]="'indeterminate'">

        </md-progress-spinner>
      </ng-template>
  </loading-content-template>
  <no-content-template>
      <!-- Show a message if there are no process definitions associated with process app -->
      <ng-template>
        <div class="no-content-message">
          {{ 'PPROCDEF_LIST.NONE' | translate }}
        </div>
      </ng-template>
  </no-content-template>
</adf-datatable>
<adf-context-menu-holder></adf-context-menu-holder>

 

We can divide the template into 2 main sections. The top section contains the ADF toolbar that displays the title for the Process Definitions page. The toolbar also contains a close button that will take you back to the Process App Details page. The second section contains the <adf-datatable> that will display the process definitions.

 

The ADF Data Table can be configured with a number of properties and event handlers. They have the following meaning (default value inside parenthesis):

 

  • data: holds the data that should be displayed in the table. It refers to a Data Table Adapter. There are many of those and you can implement your own feeding the table from whatever data source. For our implementation we will us an out-of-the-box ObjectDataTableAdapter.
  • showHeader: set to true to display data table column headers.
  • selectionMode (single): determines how many rows you can select at a time. Single row (default), multiple rows, or none.
  • multiselect (false): displays checkboxes if set to true. 
  • loading (false): set to true when the table is loading data.
  • actions: actions are menu items in the “three dots” menu that you can see to the right on each data table row. This property just controls if this menu should be available/visible or not. So set to true to display this menu.
    • (showRowActionsMenu): if actions=true, then this event handler function will be called just before the “three dots” menu is about to be displayed, so you get a chance to define the menu items to display.
    • actionsPosition: set to 'right' to display the “three dots” menu on the right side of the data row, or set to ‘left’.
    • (executeRowAction): event handler function is called when one of the “three dots” menu items are clicked on. Only relevant if actions=true, and the showRowActionsMenu handler has been implemented to provide a menu.
  • contextMenu: these are menu items in the “drop down” menu that you can see when right-clicking on a data table row. This property just controls if this menu should be available/visible or not. So set to true to display this menu when right clicking.
    • (showRowContextMenu): if contextMenu=true, then this event handler function will be called just before the “drop down” menu is about to be displayed, so you get a chance to define the menu items to display.
  • (rowClick): event handler function will be called if you click a data row

 

The next thing we need to configure in the ADF Data Table component is the <data-columns> that should be displayed, and link them to the data properties. Columns can be defined either declarative, as we do here, or programmatically from the component class. We link each column to the data via the key property, which need to match a property in the ObjectDataTableAdapter. We will load it with ProcessDefinitionRepresentation objects that looks like this:

 

export class ProcessDefinitionRepresentation {
  id: string;
  name: string;
  description: string;
  key: string;
  category: string;
  version: number;
  deploymentId: string;
  tenantId: string;
  metaDataValues: any[];
  hasStartForm: boolean;

 

 So we can see that the data column keys we use match properties in the ProcessDefinitionRepresentation. After the data column definitions we got one template for what should be displayed while data table is being loaded and one template for what should be displayed when there is no data to display.

 

The last thing in the template is the <adf-context-menu-holder> element that is a holder for where the drop down context menu should be output/displayed.

 

Now let’s implement the process definition list component class. Open up the src/app/process-apps/process-definitions-list-page/process-definitions-list-page.component.ts file and update it as follows:

 

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { ProcessService, ProcessDefinitionRepresentation } from 'ng2-activiti-processlist';
import { TaskListService } from 'ng2-activiti-tasklist';

import { ObjectDataTableAdapter, ObjectDataRow, DataCellEvent, DataRowActionEvent, DataRowEvent } from 'ng2-alfresco-datatable';

import { Subject } from 'rxjs/Subject';

@Component({
  selector: 'app-process-definitions-list-page',
  templateUrl: './process-definitions-list-page.component.html',
  styleUrls: ['./process-definitions-list-page.component.css']
})
export class ProcessDefinitionsListPageComponent implements OnInit {
  appId: string;
  appName: string;
  data: ObjectDataTableAdapter;
  isLoading = true;

  detailsMenuActionId = 'details';
  showDetailsMenuAction = {
    title: 'Show Details',
    subject: new Subject(),
    // your custom metadata needed for onExecuteThreeDotsMenuItem
    id: this.detailsMenuActionId
  };

  constructor(private router: Router,
              private activatedRoute: ActivatedRoute,
              private taskListService: TaskListService,
              private processService: ProcessService) {
    this.data = new ObjectDataTableAdapter();
  }

  ngOnInit() {
    this.showDetailsMenuAction.subject.subscribe(item => this.onExecuteDropDownMenuItem(item));

    this.appId = this.activatedRoute.snapshot.params['process-app-id'];
    console.log('Showing process definitions for app: ', this.appId);

    this.taskListService.getApplicationDetailsById(+this.appId).subscribe(
      (appDef: any) => {
        this.appName = appDef.name;
      },
      (error) => {
        console.log('Error: ', error);
      });

    this.processService.getProcessDefinitions(this.appId).subscribe(
      (procDefs: ProcessDefinitionRepresentation[]) => {
        this.isLoading = true;
        const procDefRows = this.createDataRows(procDefs);
        this.data.setRows(procDefRows);
        this.isLoading = false;
      },
      (error) => {
        console.log('Error: ', error);
      });
  }

  onShowThreeDotsMenu(event: DataCellEvent) {
    event.value.actions = [
      this.showDetailsMenuAction
    ];
  }

  onShowDropDownMenu(event: DataCellEvent) {
    const args = event.value;
    const procDefId = args.row.getValue('id');

    event.value.actions = [
      this.showDetailsMenuAction
    ];

    event.value.actions =
      event.value.actions.map(a => {
        return {
          title: a.title,
          subject: a.subject,
          id: a.id,
          procDefId: procDefId, // Inject the context (i.e. Proc Def Id)
        };
      });
  }

  onExecuteThreeDotsMenuItem(event: DataRowActionEvent) {
    const args = event.value;
    const menuActionId = args.action.id;
    const procDefId = args.row.getValue('id'); // Something like ReviewaFile:2:2506
    console.log(`ThreeDots menu item invoked: action = ${menuActionId} (${args.action.title}) procDefId = ${procDefId}`);

    this.navigate2ProcDetails(menuActionId, procDefId);
  }

  onExecuteDropDownMenuItem(menuItem) {
    const menuActionId = menuItem.id;
    const procDefId = menuItem.procDefId; // Something like ReviewaFile:2:2506
    console.log(`DropDown menu item invoked: action = ${menuActionId} (${menuItem.title}) procDefId = ${procDefId}`);

    this.navigate2ProcDetails(menuActionId, procDefId);
  }

  onRowClick(event: DataRowEvent) {
    // Not currently doing anything when row is clicked
  }

  onGoBack($event: Event) {
    this.navigateBack2AppList();
  }

  private navigateBack2AppList() {
    this.router.navigate(['../'],
      {
        relativeTo: this.activatedRoute
      });
  }

  private navigate2ProcDetails(menuActionId: string, procDefId: string) {
    if (menuActionId === this.detailsMenuActionId) {
      this.router.navigate(['../procdef-details', procDefId ],
        {
          relativeTo: this.activatedRoute
        });
    }
  }

  private createDataRows(procDefs: ProcessDefinitionRepresentation[]): ObjectDataRow[] {
    const procDefRows: ObjectDataRow[] = [];

    procDefs.forEach((procDef) => {
      procDefRows.push(new ObjectDataRow(procDef));
    });

    return procDefRows;
  }
}

 

 The first thing we do in the class is to define the appId, appName, data, and isLoading properties that are used in the template definition. The Drop Down menu and the Three Dots menu should both have one menu item called “Details”. We define it with the showDetailsMenuAction object. If you don’t plan on having a drop down menu, then you don’t need the subject property.

 

The Process Details list page is accessed via a URL that looks like  http://localhost:4200/process-apps/1/procdef-list, so at the beginning of the ngOnInit method we extract the process-app-id. We can then use the Process App ID to fetch the Process App details and all the associated Process Definitions. To fetch the Process Definitions we use a new service called ProcessService from the ng2-activiti-processlist package.

 

Note here that the getProcessDefinitions function wants the Process App id as a string, while the getApplicationDetailsById function wants the Process App id as a number.

 

When the async getProcessDefinitions call returns we create the data rows based on the ProcessDefinitionRepresentation array. Each ObjectDataRow is basically a ProcessDefinitionRepresentation, so the column keys match.

 

The rest of the code has a lot to do with the Drop Down menu and the Three Dots menu. The onShowThreeDotsMenu and onShowDropDownMenu functions are used to configure what menu items to display for each menu. The onShowThreeDotsMenu just sets the actions (i.e. menu items) to what we configured in the beginning. When an item is clicked in the Three Dots menu context will be automatically available (i.e. what Process Definition that was clicked). However, with the Drop Down menu you don't get context when it is clicked, we need to inject it when creating the menu.

 

When the Details menu item is clicked in either menu we navigate to the Process Definitions Details page, which we will implement in the next section.

 

However, before we implement the details page we should fix the navigation to the Process Definition list from the Process App details page. Open up the src/app/process-apps/process-apps-details-page/process-apps-details-page.component.ts file and implement the onShowProcDefs event handler as follows:

 

onShowProcDefs($event: Event) {
    console.log('Navigate to process definitions for app: ', this.appId);

    this.router.navigate(['procdef-list'],
      {
        relativeTo: this.activatedRoute
      });
  }

 

Displaying Process Definition Details

Now we can move on and implement the Process Definition Detail component that will display both the properties for the process definition and a diagram of the process model. To display the diagram we will use another ADF Process Services component in the ng2-activiti-diagrams package. Install it as follows:

 

Martins-MacBook-Pro::adf-workbench-process mbergljung$ npm install --save-exact ng2-activiti-diagrams@1.9.0

+ ng2-activiti-diagrams@1.9.0

 

Note that this package depends on the raphael package, which will be installed automatically as it is a transitive dependency.

 

There are assets/resources that we need to bring in so Webpack includes them in the app. Do this in .angular-cli.json:

 

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "adf-workbench"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico",
        { "glob": "**/*", "input": "../node_modules/ng2-alfresco-core/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-alfresco-login/bundles/assets", "output": "./assets/" },
        { "glob": "**/*", "input": "../node_modules/ng2-activiti-tasklist/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-activiti-form/bundles/assets", "output": "./assets/" },
        { "glob": "**/*", "input": "../node_modules/ng2-alfresco-datatable/bundles/assets", "output": "./assets/" },
        { "glob": "**/
*", "input": "../node_modules/ng2-activiti-processlist/bundles/assets", "output": "./assets/" },
        { "glob": "**/*", "input": "../node_modules/ng2-activiti-diagrams/bundles/assets", "output": "./assets/" }
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.css",
        "../node_modules/material-design-lite/dist/material.orange-blue.min.css",
        "../node_modules/ng2-alfresco-core/prebuilt-themes/adf-blue-orange.css"
      ],
      "scripts": [
        "../node_modules/material-design-lite/material.min.js",
        "../node_modules/raphael/raphael.js"
      ],

 

Note that we bring in both the assets for the ng2-activiti-diagrams package and we load the raphael script

The new DiagramsModule need to be made available to our ProcessAppsModule. Open up the src/app/process-apps/process-apps.module.ts file and add it:

 

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ProcessAppsRoutingModule } from './process-apps-routing.module';
import { ProcessAppsPageComponent } from './process-apps-page/process-apps-page.component';
import { ProcessAppsListPageComponent } from './process-apps-list-page/process-apps-list-page.component';
import { ProcessAppsDetailsPageComponent } from './process-apps-details-page/process-apps-details-page.component';
import { ProcessDefinitionsListPageComponent } from './process-definitions-list-page/process-definitions-list-page.component';
import { ProcessDefinitionsDetailsPageComponent } from './process-definitions-details-page/process-definitions-details-page.component';

import { AppCommonModule } from '../app-common/app-common.module';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { ActivitiProcessListModule } from 'ng2-activiti-processlist';
import { DataTableModule } from 'ng2-alfresco-datatable';
import { DiagramsModule } from 'ng2-activiti-diagrams';

import { CardViewUpdateService } from 'ng2-alfresco-core';

@NgModule({
  imports: [
    CommonModule,
    ProcessAppsRoutingModule,

    /* Common App imports (Angular Core and Material, ADF Core */
    AppCommonModule,

    /* ADF libs specific to this module */
    ActivitiTaskListModule,
    ActivitiProcessListModule,
    DataTableModule,
    DiagramsModule
  ],
  declarations: [ProcessAppsPageComponent, ProcessAppsListPageComponent, ProcessAppsDetailsPageComponent,
    ProcessDefinitionsListPageComponent, ProcessDefinitionsDetailsPageComponent],
  providers: [CardViewUpdateService] /* Need to set it up as a provider here as there is a bug in CoreModule, it does not import... */
})
export class ProcessAppsModule { }

 

To implement the Process Definition details page start with the template. Open up the src/app/process-apps/process-definitions-details-page/process-definitions-details-page.component.html file and make it look like this:

 

<adf-toolbar [color]="'accent'">
  <adf-toolbar-title>
    <span *ngIf="appName">Process Definition '{{ procDefName }}' associated with app '{{ appName }}'</span>
  </adf-toolbar-title>
  <adf-toolbar-divider></adf-toolbar-divider>
  <button md-icon-button
          class="adf-viewer-close-button"
          mdTooltip="Close and go back to Process Def list"
          (click)="onGoBack($event)"
          aria-label="Close">

    <md-icon>close</md-icon>
  </button>
</adf-toolbar>
<md-tab-group>
  <md-tab label="Properties">
    <md-card class="adf-card-container">
      <md-card-content>
        <adf-card-view
          [properties]="properties"
          [editable]="false">

        </adf-card-view>
      </md-card-content>
    </md-card>
  </md-tab>
  <md-tab label="Preview">
    <adf-diagram
      [processDefinitionId]="procDefId">

    </adf-diagram>
  </md-tab>
</md-tab-group>

 

Here we use a layout that is very similar to the one we used for the Repository Details page in the Building a Content Management app article. We got the familiar ADF toolbar at the top and then we got a tabbed view that is implemented with Angular Material components (i.e. md-). The first tab will contain the process definition details/properties and the second tab will contain a preview of the process model/diagram.

 

To display the process model diagram we use a new ADF component called <adf-diagram> that is part of the ng2-activiti-diagrams package. This component takes only the Process Definition ID and then draws a diagram with the help of Raphael, a cross-browser vector graphics package.

 

The properties tab is implemented with the <adf-card-view, which we have seen before in both this article and in the Building a Content Management app article.

If you need a more sophisticated approach for your properties layout, then have a look at the ADF Form component. It is described in the Content Management App article.

The component backing class is implemented in the src/app/process-apps/process-definitions-details-page/process-definitions-details-page.component.ts file and looks like this:

 

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { CardViewItem, CardViewTextItemModel } from 'ng2-alfresco-core';

import { ProcessService, ProcessDefinitionRepresentation } from 'ng2-activiti-processlist';
import { TaskListService } from 'ng2-activiti-tasklist';

@Component({
  selector: 'app-process-definitions-details-page',
  templateUrl: './process-definitions-details-page.component.html',
  styleUrls: ['./process-definitions-details-page.component.css']
})
export class ProcessDefinitionsDetailsPageComponent implements OnInit {
  appName: string;
  procDefId: string;
  procDefName: string;
  properties: Array<CardViewItem>;

  constructor(private router: Router,
              private activatedRoute: ActivatedRoute,
              private taskListService: TaskListService,
              private processService: ProcessService) {
    this.properties = new Array<CardViewItem>();
  }

  ngOnInit() {
    const appId = this.activatedRoute.snapshot.params['process-app-id'];
    this.procDefId = this.activatedRoute.snapshot.params['process-def-id'];
    console.log('Showing process definition for app: ', appId, ' and process def ID ', this.procDefId);

    this.taskListService.getApplicationDetailsById(+appId).subscribe(
      (appDef: any) => {
        this.appName = appDef.name;
      },
      (error) => {
        console.log('Error: ', error);
      });

    this.processService.getProcessDefinitions(appId).subscribe(
      (procDefs: ProcessDefinitionRepresentation[]) => {
        for (const pd of procDefs) {
          if (this.procDefId === pd.id) {
            this.procDefName = pd.name;
            this.setupProps(pd);
          }
        }
      },
      (error) => {
        console.log('Error: ', error);
      });
  }

  onGoBack($event: Event) {
    this.navigateBack2AppList();
  }

  private navigateBack2AppList() {
    this.router.navigate(['../../procdef-list'],
      {
        relativeTo: this.activatedRoute
      });
  }

  private setupProps(procDef: ProcessDefinitionRepresentation) {
    console.log('setupProps: ', procDef.id);

    const idProp = new CardViewTextItemModel({label: 'Id:', value: procDef.id, key: 'procDefId'});
    const nameProp = new CardViewTextItemModel({label: 'Name:', value: procDef.name, key: 'procDefName'});
    const descProp = new CardViewTextItemModel({label: 'Description:', value: procDef.description, key: 'procDefDesc'});
    const keyProp = new CardViewTextItemModel({label: 'Key:', value: procDef.key, key: 'procDefKey'});
    const categoryProp = new CardViewTextItemModel({label: 'Category:', value: procDef.category, key: 'procDefCategory'});
    const verProp = new CardViewTextItemModel({label: 'Version:', value: procDef.version, key: 'procDefVer'});
    const deploymentIdProp = new CardViewTextItemModel({label: 'Deployment Id:', value: procDef.deploymentId, key: 'deploymentId'});
    const tenantIdProp = new CardViewTextItemModel({label: 'Tenant Id:', value: procDef.tenantId, key: 'tenantId'});
    const hasStartFormProp = new CardViewTextItemModel({label: 'Has start form:', value: procDef.hasStartForm, key: 'hasStartForm'});

    this.properties.push(idProp);
    this.properties.push(nameProp);
    this.properties.push(descProp);
    this.properties.push(keyProp);
    this.properties.push(categoryProp);
    this.properties.push(verProp);
    this.properties.push(deploymentIdProp);
    this.properties.push(tenantIdProp);
    this.properties.push(hasStartFormProp);

    if (procDef.metaDataValues) {
      let metadataCount = 0;
      for (const metaDataValue of procDef.metaDataValues) {
        this.properties.push(new CardViewTextItemModel({
          label: 'Metadata (' + metadataCount + '):',
          value: metaDataValue,
          key: 'metadata' + metadataCount }));
        metadataCount++;
      }
    }
  }
}

 

As usual the class starts by defining the properties appName, procDefId, procDefName, and properties that are referred to in the template. The ngOnInit function is very similar to what we did for the Process Definition list component class. There’s no API available for fetching just one Process Definition, so we fetch all and pick the one that matches passed in process-def-id.

 

It is then just a matter of setting up the ADF Card View properties, which will match what we got in the ProcessDefinitionRepresentation object.

 

That’s pretty much it. To see the new Process Definitions list and details pages restart the server.  

Starting Processes from a Process App

Now that we got a list of process definitions it is most likely that we want to start process instances based on them. The easiest way to do this is actually to use an ADF component called <adf-start-process. This component is part of the ng2-activiti-processlist package, so we already got everything installed to start using it. And the ActivitiProcessListModule is also already imported into the ProcessAppsModule.

 

The Start Process component takes a Process App ID as input, so it make sense to use it when we already got a Process App ID, which we do in for example the Process App List page or the Process App Details page. Let’s add a Start Process button to the toolbar in the Process App Details page, between the Show Process Definitions button and the Close button, it should look something like this:



 

This button will trigger a navigation to a new page that we will call Start Process, here is how it will look like:

 

 

This Start Process form is automatically generated when we use the <adf-start-process> component. The Type drop down will be pre populated with the Process Definitions that have been associated with the Process App:



Clicking one of the Process Definitions, such as Invoice Approval Process in this case, immediately shows the associated Start Form:

 

 

Here we fill in the specific fields for the Process Definition's start form. Then click the Start Process button at the bottom of the form. If the process instance is started successfully we will display a message at the bottom of the screen. If the process instance could not be started, then we display an error message at the bottom of the screen.

Generating a Start Process page component

The Start Process page component will all be part of the ProcessAppsModule. As usual, we can easily create the needed component with the Angular CLI tool:

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ cd src/app/process-apps/

 

Martins-MacBook-Pro:process-apps mbergljung$ ng g component start-process-page

  create src/app/process-apps/start-process-page/start-process-page.component.css (0 bytes)

  create src/app/process-apps/start-process-page/start-process-page.component.html (37 bytes)

  create src/app/process-apps/start-process-page/start-process-page.component.spec.ts (700 bytes)

  create src/app/process-apps/start-process-page/start-process-page.component.ts (315 bytes)

  update src/app/process-apps/process-apps.module.ts (1910 bytes)

 

This creates a Start Process page component where a list of process definitions will be displayed so the user can start a Process Instance from one of them.

 

We need to set up a new route configuration with a child route for the Start Process page. Let’s add it in the src/app/process-apps/process-apps-routing.module.ts file as follows:

 

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ProcessAppsPageComponent } from './process-apps-page/process-apps-page.component';
import { ProcessAppsListPageComponent } from './process-apps-list-page/process-apps-list-page.component';
import { ProcessAppsDetailsPageComponent } from './process-apps-details-page/process-apps-details-page.component';
import { ProcessDefinitionsListPageComponent } from './process-definitions-list-page/process-definitions-list-page.component';
import { ProcessDefinitionsDetailsPageComponent } from './process-definitions-details-page/process-definitions-details-page.component';
import { StartProcessPageComponent } from './start-process-page/start-process-page.component';

import { AuthGuardBpm } from 'ng2-alfresco-core';

const routes: Routes = [ {
  path: 'process-apps',
  component: ProcessAppsPageComponent,
  canActivate: [AuthGuardBpm],
  data: {
    title: 'Process Apps',
    icon: 'apps',
    hidden: false,
    needBpmAuth: true,
    isLogin: false
  },
  children: [
    { path: '', component: ProcessAppsListPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id', component: ProcessAppsDetailsPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id/procdef-list', component: ProcessDefinitionsListPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id/procdef-details/:process-def-id', component: ProcessDefinitionsDetailsPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-app-id/start-process', component: StartProcessPageComponent, canActivate: [AuthGuardBpm] }
  ]
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProcessAppsRoutingModule { }

 

We configure the route to the new page component to be a child of the Process App parent component route. This means we can use the same router-outlet as before. 

Implementing Navigation to the Start Process Page

The user will be able to navigate to the Start Process page from the Process App Details page. A new button should be added called Start Process. Open up the src/app/process-apps/process-apps-details-page/process-apps-details-page.component.html file and add it as follows:

 

<adf-toolbar [color]="'accent'">
  <adf-toolbar-title>
      <span *ngIf="appName">{{ appName }}</span>
  </adf-toolbar-title>
  <button md-icon-button
          mdTooltip="Show Process Definitions included in app"
          (click)="onShowProcDefs($event)">

    <md-icon>device_hub</md-icon>
  </button>
  <button md-icon-button
          mdTooltip="Start Process based on Process Definitions for App"
          (click)="onStartProcess($event)">

    <md-icon>launch</md-icon>
  </button>
  <adf-toolbar-divider></adf-toolbar-divider>
  <button md-icon-button
          class="adf-viewer-close-button"
          mdTooltip="Close and go back to Process App list"
          (click)="onGoBack($event)"
          aria-label="Close">

    <md-icon>close</md-icon>
  </button>
</adf-toolbar>
<md-card class="adf-card-container">
  <md-card-content>
    <adf-card-view
      [properties]="properties"
      [editable]="false">
 
    </adf-card-view>
  </md-card-content>
</md-card>

 

Implement the onStartProcess handler as follows in the src/app/process-apps/process-apps-details-page/process-apps-details-page.component.ts file:

 

  onStartProcess($event: Event) {
    console.log('Navigate to start process for app: ', this.appId);

    this.router.navigate(['start-process'],
      {
        relativeTo: this.activatedRoute
      });
  }

 

Implementing the Start Process Page

Now we can move on and implement the Start Process page component. Start with the template, open up the src/app/process-apps/start-process-page/start-process-page.component.html file and replace whatever is there with:

 

<adf-toolbar [color]="'accent'">
  <adf-toolbar-title>
    <span *ngIf="appName">Start a process for '{{ appName }}'</span>
  </adf-toolbar-title>
  <adf-toolbar-divider></adf-toolbar-divider>
  <button md-icon-button
          mdTooltip="Close and go back to Process App list"
          (click)="onGoBack($event)"
          aria-label="Close">

    <md-icon>close</md-icon>
  </button>
</adf-toolbar>
<adf-start-process
  [appId]="appId"
  (start)="onStartProcessInstance($event)"
  (cancel)="onCancelProcessInstance()"
  (error)="onStartError($event)">

</adf-start-process>

 

So we have the usual ADF toolbar at the top with a page title and Close button. Then under it we use the ADF Start Process component. It takes just the Process App ID and then it provides three event handlers for doing stuff depending on if it was successful to start the process or not.

 

The backing component class goes into the src/app/process-apps/start-process-page/start-process-page.component.ts file and looks like this:

 

import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { NotificationService } from 'ng2-alfresco-core';
import { TaskListService } from 'ng2-activiti-tasklist';
import { StartProcessInstanceComponent, ProcessInstance } from 'ng2-activiti-processlist';

@Component({
  selector: 'app-start-process-page',
  templateUrl: './start-process-page.component.html',
  styleUrls: ['./start-process-page.component.css']
})
export class StartProcessPageComponent implements OnInit {
  appId: number;
  appName: string;

  @ViewChild(StartProcessInstanceComponent)
  startProcessForm: StartProcessInstanceComponent;

  constructor(private router: Router,
              private activatedRoute: ActivatedRoute,
              private taskListService: TaskListService,
              private notificationService: NotificationService) { }

  ngOnInit() {
    this.appId = +this.activatedRoute.snapshot.params['process-app-id'];
    console.log('Start Process for app: ', this.appId);
    this.taskListService.getApplicationDetailsById(this.appId).subscribe(
      (appDef: any) => {
        this.appName = appDef.name;
      },
      (error) => {
        console.log('Error: ', error);
      });
  }

  onGoBack($event: Event) {
    this.navigateBack2AppList();
  }

  private navigateBack2AppList() {
    this.router.navigate(['../'],
      {
        relativeTo: this.activatedRoute
      });
  }

  onStartProcessInstance(procInstance: ProcessInstance) {
    console.log('Started process instance: ', procInstance.id);
    this.notificationService.openSnackMessage(
      `Successfully started process instance ('${procInstance.id}') for Process App ${this.appName}`,
      4000);
    this.startProcessForm.reset();

  }

  onCancelProcessInstance() {
    console.log('Starting Process was cancelled.');
    this.startProcessForm.reset();
  }

  onStartError(error: any) {
    console.log('There was an error starting process: ', error);
    this.notificationService.openSnackMessage(
      `Failed to start process instance for Process App ${this.appName} error = ${error}`,
      4000);
  }
}

 

The ngOnInit function is where we extract the Process App ID and then fetch the Process App data for it, mainly to get to the Process App name, so we can display it in toolbar. The event handler implementations are quite straightforward. Except that we need to reset the Start Form after we have started a new process instance, and also if the user cancels the form. We can get our hands on the start form by using the @ViewChild decorator.

 

When a Process Instance is started successfully a message will be displayed with the ADF Notification Service. There will also be a console log with the Process Instance ID.

 

Now try this out and start a few process instances so you have them available when continuing the tutorial, process instances and task instances are necessary for the rest of the article.

Managing My Tasks

This part of the application is about interacting with ongoing tasks. This means looking at task instances for different process instances. There are a number of ADF components that can be used for this. What task instances that are displayed depend on what user that is logged in.

 

We will have a new menu item called My Tasks in the left navigation menu that will display a page looking something like this:

 

 

This page displays the Process Applications that the logged in user has access to. The user then need to click on one of the Apps to see any tasks instances assigned to them:

 

 

By default all tasks that are assigned to the user will be displayed. If a task has a checklist (i.e sub-tasks), then those will be displayed without process instance ID as in above list.

 

If the process instances contains pooled tasks (i.e. candidate tasks that need to be claimed before worked on) then the user has to click on the Open (Pooled) radio button to see if any exist that the logged in user can claim.

 

If the user wants to see tasks that he or she has completed, then click on the Completed radio button. To manage a task the user clicks on the row, which will display a detail page with options to manage the task, or just view details.

Generating the My Tasks module and page components

We are going to need a My Tasks parent page component. It will contain two child page components, one for the Process Apps List and one for the Process Apps Details page 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-process directory do:

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ ng g module my-tasks --flat false --routing

  create src/app/my-tasks/my-tasks-routing.module.ts (250 bytes)

  create src/app/my-tasks/my-tasks.module.ts (284 bytes)

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ cd src/app/my-tasks/

 

Martins-MacBook-Pro:my-tasks mbergljung$ ng g component my-tasks-page

  create src/app/my-tasks/my-tasks-page/my-tasks-page.component.css (0 bytes)

  create src/app/my-tasks/my-tasks-page/my-tasks-page.component.html (32 bytes)

  create src/app/my-tasks/my-tasks-page/my-tasks-page.component.spec.ts (665 bytes)

  create src/app/my-tasks/my-tasks-page/my-tasks-page.component.ts (295 bytes)

  update src/app/my-tasks/my-tasks.module.ts (384 bytes)

 

Martins-MacBook-Pro:my-tasks mbergljung$ ng g component my-tasks-list-page

  create src/app/my-tasks/my-tasks-list-page/my-tasks-list-page.component.css (0 bytes)

  create src/app/my-tasks/my-tasks-list-page/my-tasks-list-page.component.html (37 bytes)

  create src/app/my-tasks/my-tasks-list-page/my-tasks-list-page.component.spec.ts (694 bytes)

  create src/app/my-tasks/my-tasks-list-page/my-tasks-list-page.component.ts (314 bytes)

  update src/app/my-tasks/my-tasks.module.ts (504 bytes)

 

Martins-MacBook-Pro:my-tasks mbergljung$ ng g component my-tasks-details-page

  create src/app/my-tasks/my-tasks-details-page/my-tasks-details-page.component.css (0 bytes)

  create src/app/my-tasks/my-tasks-details-page/my-tasks-details-page.component.html (40 bytes)

  create src/app/my-tasks/my-tasks-details-page/my-tasks-details-page.component.spec.ts (715 bytes)

  create src/app/my-tasks/my-tasks-details-page/my-tasks-details-page.component.ts (326 bytes)

  update src/app/my-tasks/my-tasks.module.ts (636 bytes)

 

This creates a My Tasks module with routing, a My Tasks parent page, and a My Tasks list page component where we will display the available task instances for the user. It also creates a My Tasks details page where we can display more detailed information about a selected task, such as management view, properties, comments etc.

 

We need to set up a new route configuration with a parent route that has two child routes for the list page and details page. Let’s add it in the src/app/my-tasks/my-tasks-routing.module.ts file as follows:

 

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { MyTasksPageComponent } from './my-tasks-page/my-tasks-page.component';
import { MyTasksListPageComponent } from './my-tasks-list-page/my-tasks-list-page.component';
import { MyTasksDetailsPageComponent } from './my-tasks-details-page/my-tasks-details-page.component';

import { AuthGuardBpm } from 'ng2-alfresco-core';

const routes: Routes = [ {
  path: 'my-tasks',
  component: MyTasksPageComponent,
  canActivate: [AuthGuardBpm],
  data: {
    title: 'My Tasks',
    icon: 'assignment',
    hidden: false,
    needBpmAuth: true,
    isLogin: false
  },
  children: [
    { path: '', component: MyTasksListPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':task-id', component: MyTasksDetailsPageComponent, canActivate: [AuthGuardBpm] }
  ]
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class MyTasksRoutingModule { }

 

The parent page route is http://localhost:4200/my-tasks, and if we don’t specify any other URL path elements, then the the My Tasks List child page will be displayed automatically as it has path ''. The Task Details page is accessed via the http://localhost:4200/my-tasks/<task-id> URL.

 

We also need to make the new pages and routes 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 { ProcessAppsModule } from './process-apps/process-apps.module';
import { ProcessAppsRoutingModule } from './process-apps/process-apps-routing.module';
import { MyTasksModule } from './my-tasks/my-tasks.module';
import { MyTasksRoutingModule } from './my-tasks/my-tasks-routing.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,

    AppCommonModule,
    AppLoginModule,
    AppLoginRoutingModule,
    ProcessAppsModule,
    ProcessAppsRoutingModule,
    MyTasksModule,
    MyTasksRoutingModule
  ],
  providers: [AppMenuService],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

This section makes heavy use of the Activiti Task List module and Angular Material components. These components are not yet known to the My Tasks module. Import as follows in the src/app/my-tasks/my-tasks.module.ts file:

 

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { MyTasksRoutingModule } from './my-tasks-routing.module';
import { MyTasksPageComponent } from './my-tasks-page/my-tasks-page.component';
import { MyTasksListPageComponent } from './my-tasks-list-page/my-tasks-list-page.component';
import { MyTasksDetailsPageComponent } from './my-tasks-details-page/my-tasks-details-page.component';

import { AppCommonModule } from '../app-common/app-common.module';

import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';


@NgModule({
  imports: [
    CommonModule,
    MyTasksRoutingModule,

    /* Common App imports (Angular Core and Material, ADF Core */
    AppCommonModule,

    /* ADF libs specific to this module */
    ActivitiTaskListModule
  ],
  declarations: [MyTasksPageComponent, MyTasksListPageComponent, MyTasksDetailsPageComponent]
})
export class MyTasksModule { }

 

Here we import the ActivitiTaskListModule, which gives us access to the Process App component and many task list and details components. At the same time we also import the AppCommonModule that we got from the starter project we cloned at the beginning. It gives us access to the ADF Core module and the Angular Material components.

 

The output from the My Tasks list and details page need to go somewhere. Update the main My Tasks page template in the src/app/my-tasks/my-tasks-page/my-tasks-page.component.html file as follows, replace whatever markup is already there:

 

<router-outlet></router-outlet>

 

This finishes off the setup of the my tasks module.

Implementing the My Tasks List component

Now we can move on and implement the My Tasks List component that will provide a list of the applications that the logged in user has access to. The user can then click on one of the Apps and the related assigned task instances will be displayed in a list. The user will also have the option to display pooled and completed tasks via radio buttons.

 

To do this we are going to use a new ADF component called <adf-tasklist that is part of the ng2-activiti-tasklist package. This package is already installed so we are ready to start implementing the component.

 

Let’s start with the page template for the Task List. Open up the src/app/my-tasks/my-tasks-list-page/my-tasks-list-page.component.html file and replace whatever is there with the following:

 

<adf-toolbar [color]="'accent'">
  <adf-toolbar-title>
    <span *ngIf="appName">My Tasks for '{{ appName }}'</span>
    <span *ngIf="!appName">==>> Select an App to see tasks </span>
  </adf-toolbar-title>
</adf-toolbar>
<adf-apps
  [layoutType]="'LIST'"
  [filtersAppId]="processAppsFilter"
  (appClick)="onAppClick($event)">

</adf-apps>
<md-divider></md-divider>
<div class="margin10" *ngIf="appName">
  Show tasks that are:
  <md-radio-group (change)="onSelectTaskType($event.value)" class="margin10">
    <md-radio-button value="openAssigned" checked="true">Open (Assigned)</md-radio-button>
    <md-radio-button value="openPooled">Open (Pooled)</md-radio-button>
    <md-radio-button value="completed">Completed</md-radio-button>
  </md-radio-group>
</div>
<md-tab-group>
  <md-tab [label]="taskTypeName">
    <adf-tasklist *ngIf="appId"
                  [appId]="appId"
                  [state]="taskState"
                  [assignment]="taskAssignment"
                  (rowClick)="onTaskClick($event)">

      <data-columns>
        <data-column key="processInstanceId" title="Process Id"></data-column>
        <data-column key="id" title="Task Id"></data-column>
        <data-column key="name" title="Name" class="full-width name-column"></data-column>
        <data-column key="created" title="Created" type="date"></data-column>
      </data-columns>
    </adf-tasklist>
  </md-tab>
</md-tab-group>

 

We are using familiar ADF components for the toolbar to display the page title. Then, to display the Process Apps, we use the <adf-apps component that we got familiar with in the beginning of this article. After that we got the three radio buttons that allow us to switch between Assigned, Pooled, and Completed tasks for the currently logged in user.

 

As you can see, we use Angular Material components (i.e. md-) to display the radio button group. These Angular Material components have already been imported and provided via our AppCommonModule that we created in the beginning.

 

Now to the new ADF component <adf-tasklist, it takes the following properties and event handlers:

 

  • Task Query Filters:
    • appId: the Process Application id. Task instances will only be displayed if they belong to process instances associated with this process app.
    • processInstanceId: the Process Instance ID. Task instances will only be displayed if they belong to this Process Instances.
    • state: task instances in this state will be included. Can be active or completed.  
    • assignment: task instances will be included if they have this type of assignment. Can be assignee (task is assigned to current user), candidate (this is a pooled task that current user can claim), empty (include all).
    • name: include task only if it matches this name. Name is from TaskDetailsModel below.
  • sort: sort the task list by this property. For example 'created-desc'. See TaskDetailsModel below.
  • landingTaskId: the task instance row that should be selected in the list.
  • data: the ObjectDataTableAdapter that is backing the Task List.
  • selectionMode (none): determines how many rows you can select at a time. single row, multiple rows, or none.
  • multiselect (false): displays checkboxes if set to true
  • page (0): what page to display if there are more tasks then the page size.
  • size (5): the page size, how many tasks should be displayed per page in the task list.
  • (rowClick): called when one of the task rows is clicked.
  • (rowSelected): called when one of the tasks rows is selected.
  • (onSuccess): called when a task has been successfully completed.
  • (onError): called when a task could NOT be successfully completed.

 

There are many more properties and events listed here than we use. So it is possible to customise the Task List quite a bit. As with all the lists that are based on the ADF Data table it is possible to customise what columns to display. Here we are doing that to be able to show process id, task id, and created date. The <data-column key need to match a value in the TaskDetailsModel:

 

export class TaskDetailsModel {
  id: string;
  name: string;
  assignee: LightUserRepresentation;
  priority: number;
  adhocTaskCanBeReassigned: number;
  category: string;
  created: string;
  description: string;
  dueDate: string;
  duration: string;
  endDate: string;
  executionId: string;
  formKey: string;
  initiatorCanCompleteTask: boolean;
  managerOfCandidateGroup: boolean;
  memberOfCandidateGroup: boolean;
  memberOfCandidateUsers: boolean;
  involvedPeople: LightUserRepresentation [];
  parentTaskId: string;
  parentTaskName: string;
  processDefinitionCategory: string;
  processDefinitionDeploymentId: string;
  processDefinitionDescription: string;
  processDefinitionId: string;
  processDefinitionKey: string;
  processDefinitionName: string;
  processDefinitionVersion: number = 0;
  processInstanceId: string;
  processInstanceName: string;
  processInstanceStartUserId: string;
  taskDefinitionKey: string;

 

The last configuration for the task list is an event to capture when a task in the list is clicked. The onTaskClick method is then called so we can navigate to the Task Details page.

 

The template uses a CSS class called margin10, which we need to add. Let's add it to the global app stylesheet in the src/styles.css file so we can use it in any component template:

 

.margin10 {
  margin: 10px;
}

 

The template is now finished so we can move on and implement the backing component class. Open up the src/app/my-tasks/my-tasks-list-page/my-tasks-list-page.component.ts file and update it as follows:

 

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { AppDefinitionRepresentationModel } from 'ng2-activiti-tasklist';

@Component({
  selector: 'app-my-tasks-list-page',
  templateUrl: './my-tasks-list-page.component.html',
  styleUrls: ['./my-tasks-list-page.component.css']
})
export class MyTasksListPageComponent implements OnInit {
  appId: number;
  appName: string;
  taskTypeName: string;
  taskState: string;
  taskAssignment: string;

  /* Create a filter that will exclude the Task List App and include only new custom apps */
  processAppsFilter = [ { tenantId: 1 } ];

  constructor(private router: Router) {
    this.taskTypeName = 'Open (Assigned)';
    this.taskState = 'active';
    this.taskAssignment = 'assignee';
  }

  ngOnInit() {
  }

  onSelectTaskType(displayTaskType: string) {
    console.log('Changed task type to show : ', displayTaskType);

    if (displayTaskType === 'openAssigned') {
      this.taskTypeName = 'Open (Assigned)';
      this.taskState = 'active';
      this.taskAssignment = 'assignee';
    } else if (displayTaskType === 'openPooled') {
      this.taskTypeName = 'Open (Pooled)';
      this.taskState = 'active';
      this.taskAssignment = 'candidate';
    } else if (displayTaskType === 'completed') {
      this.taskTypeName = 'Completed';
      this.taskState = 'completed';
      this.taskAssignment = '';
    }
  }

  onAppClick(appDef: AppDefinitionRepresentationModel) {
    console.log('Task state: ' , this.taskState, ' assignment : ', this.taskAssignment, ' Selected App : ', appDef);

    if (appDef) {
      this.appId = appDef.id;
      this.appName = appDef.name;
    }
  }

  onTaskClick(id: string) {
    console.log('Navigating to Task Details : ', id);

    this.router.navigate(['/my-tasks', id]);
  }
}

 

As usual, we start of by defining the appId, appName, taskTypeName, taskState, and  taskAssignment template variables. In the constructor we set up what type of tasks we want to display by default, in this case tasks assigned to current user. The onSelectTaskType function is triggered whenever we click one of the radio buttons. We then set what type of tasks to display. The underlying task list is automatically updated as these are @Input variables.

 

No tasks are displayed unless a Process App is selected. The radio buttons are not visible either. When an App is selected the onAppClick function is called from which we set the internal app ID and name.

 

When a task row is clicked the onTaskClick function is called, which takes the user to the Task Details page.

 

This finishes off the task list page. Let’s move on with the task details page.

Implementing the Task Details component

When the user clicks on one of the Task Instance rows in the Task List a details page such as the following should be displayed:



 

The page will have an orange toolbar with a title and a close button. Under the toolbar will be a tabbed view of the Task Details. The first tab, which is called Manage, will display the Task Instance form from which the user can interact with the task and complete it. In this case the Invoice was not approved and the user need to clarify something or reject it all together via the CLARIFIED AND REJECTED buttons at the bottom of the form (i.e. the outcomes). 

 

Let’s start with the details page template, open up the src/app/my-tasks/my-tasks-details-page/my-tasks-details-page.component.html file and update it so it looks like this:

 

<div class="task-details-view">
  <adf-toolbar [color]="'accent'">
    <adf-toolbar-title>
      <span *ngIf="taskDetails">Task Details for '{{ taskDetails.name }}' ({{ taskDetails.id }})</span>
    </adf-toolbar-title>
    <adf-toolbar-divider></adf-toolbar-divider>
    <button md-icon-button
            mdTooltip="Close and go back to task list"
            (click)="onGoBack($event)"
            aria-label="Close">

      <md-icon>close</md-icon>
    </button>
  </adf-toolbar>
  <md-tab-group *ngIf="taskDetails">
    <md-tab label="Manage">
      <adf-task-details
        [taskId]="taskDetails.id"
        [showHeader]="false"
        [showHeaderContent]="false"
        [showInvolvePeople]="false"
        [showComments]="false"
        [showChecklist]="false"
        [showFormTitle]="true"
        [showFormCompleteButton]="true"
        [showFormSaveButton]="true"
        [showFormRefreshButton]="false">

      </adf-task-details>
    </md-tab>
    <md-tab label="Properties">
      <adf-task-header
        [taskDetails]="taskDetails">

      </adf-task-header>
    </md-tab>
    <md-tab label="Attachments">
      <adf-task-attachment-list
        [taskId]="taskDetails.id"
        (attachmentClick)="onAttachmentClick()">

      </adf-task-attachment-list>
    </md-tab>
    <md-tab label="Comments">
      <adf-comments
        [taskId]="taskDetails.id"
        [readOnly]="false">

      </adf-comments>
    </md-tab>
    <md-tab label="Checklist (Sub-tasks)">
      <adf-checklist *ngIf="taskDetails.assignee"
                     [readOnly]="false"
                     [taskId]="taskDetails.id"
                     [assignee]="taskDetails.assignee.id">

      </adf-checklist>
    </md-tab>
  </md-tab-group>
</div>

 

This page has the usual ADF Toolbar at the top with task title and identifier. Under the toolbar is an Angular Material tabbed view. Each tab uses an ADF component to display different types of details for the task.

 

The first tab, called Manage, presents a task view where you can interact with the task instance. This means that you can complete the task from and advance the associated process instance accordingly. To do this we use the ADF component called <adf-task-details. This component is very powerful, you could be fine with just using it and no other components. You can see that I have turned off a lot of features by setting a number of show* variables to false. You feed this component with the task id. I have also enabled the Complete and Save buttons so the user can interact with the task. Enabling the Complete button means that you will see all configured outcomes as buttons, such as the CLARIFIED and REJECTED buttons in the screenshot earlier on.

 

Note that if the task is a pooled task (candidate task), which means you need to claim it before you can interact with it, then a message will tell you to claim it before you can manage it. You can claim it under the Properties tab, which is next.

 

Note. It would be possible to also use <adf-form and give it task id. That is actually what is underneath an <adf-task-details component. However, it would not take care of tasks without a form, such as checklist sub-tasks and others. Using <adf-task-details takes care of it and just displays a Complete button if there is no associated form for the task instance.

 

The second tab is called Properties and displays a lot of the details for the task. It is implemented with the <adf-task-header component, which sounds like it would just display a row with for example the task title. But it actually displays a list of relevant task properties. You feed it with an object of type TaskDetailsModel:



 

At the bottom of the Properties list is a button for Requeue of a claimed task, meaning putting back the task in the pool so somebody else can claim it. If the task is not yet claimed, then there will be a Claim button.

There is a bug in ADF 1.9.0 where the Requeue button is displayed even if the task is not pooled.

The next tab, which is called Attachments, displays any uploaded files that are attached to the task instance. We do this with the <adf-task-attachment-list component. You feed this component with the task instance id:



 

If a task should offer the user the opportunity to upload more files, then you could also use the <adf-create-task-attachment component or configure drag-and-drop via the <adf-upload-drag-area component that is part of the ng2-alfresco-upload package .

 

The following tab, which is called Comments, will display any comments made by users regarding this task instance. We do this with the <adf-comments component. You feed this component with the task instance id. If you want the user to be able to add more comments, then set the readOnly property to false:



 

The last tab is called Checklist and will display any sub-tasks created for this task instance. Sometimes it is useful to divide a task into multiple steps and make sure that they have been completed. We do this with the <adf-checklist component. You feed this component with the task instance id. It also need to be fed with the assignee that we should display sub-tasks for, which would be the current user:



 

The backing component class is implemented in the src/app/my-tasks/my-tasks-details-page/my-tasks-details-page.component.ts file and looks like this:

 

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { TaskListService, TaskDetailsModel } from 'ng2-activiti-tasklist';

@Component({
  selector: 'app-my-tasks-details-page',
  templateUrl: './my-tasks-details-page.component.html',
  styleUrls: ['./my-tasks-details-page.component.css']
})
export class MyTasksDetailsPageComponent implements OnInit {
  taskDetails: TaskDetailsModel;

  constructor(private router: Router,
              private activatedRoute: ActivatedRoute,
              private taskListService: TaskListService) { }

  ngOnInit() {
    const taskId = this.activatedRoute.snapshot.params['task-id'];
    console.log('Showing task details for : ', taskId);

    this.taskListService.getTaskDetails(taskId).subscribe(
      (taskDetails: TaskDetailsModel) => {
        this.taskDetails = taskDetails;
      },
      (error) => {
        console.log('Error: ', error);
      });
  }

  onGoBack($event: Event) {
    this.navigateBack2AppList();
  }

  private navigateBack2AppList() {
    this.router.navigate(['../'],
      {
        relativeTo: this.activatedRoute
      });
  }

  onAttachmentClick() {

  }
}

 

The component class is very straightforward, it basically just fetches the Task Details for passed in ID. The task-id is passed in via the URL, which looks like http://localhost:4200/my-tasks/<task-id>. We extract the task-id in the ngOnInit method via the activedRoute object.

 

When we have the ID we use the ADF TaskListService to fetch a TaskDetailsModel object representing the details about the task.

 

We implement the navigate back handler as previously done by just stepping up to the parent route. We don’t need to pass anything back to the task list as it always justy displays all apps and then the user selects an app again.

 

That should do it, you can now try out the task list and details pages.

Managing My Processes

This part of the application is about interacting with ongoing Process Instances. There are a number of ADF components that can be used for this. What process instances that are displayed depend on what user that is logged in.

 

We will have new menu item called My Processes in the left navigation menu that will display a page looking something like this:



 

This page displays the Process Applications that the logged in user has access to. The user then need to click on one of the Apps to see the Process Instances related to that app. By default running process instances will be displayed. Clicking on the Completed radio button switches to display completed process instances that are no longer running.

Generating the My Processes module and page components

We are going to need a My Processes parent page component. It will contain two child page components, one for the Process Instance List and one for the Process Instance Details page component. Plus a module to keep everything organised.

 

As usual, we can easily create a new module and the needed components with the Angular CLI tool, standing in the

adf-workbench-process directory do:

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ ng g module my-processes --flat false --routing

  create src/app/my-processes/my-processes-routing.module.ts (254 bytes)

  create src/app/my-processes/my-processes.module.ts (300 bytes)

 

Martins-MacBook-Pro:adf-workbench-process mbergljung$ cd src/app/my-processes/

 

Martins-MacBook-Pro:my-processes mbergljung$ ng g component my-processes-page

  create src/app/my-processes/my-processes-page/my-processes-page.component.css (0 bytes)

  create src/app/my-processes/my-processes-page/my-processes-page.component.html (36 bytes)

  create src/app/my-processes/my-processes-page/my-processes-page.component.spec.ts (693 bytes)

  create src/app/my-processes/my-processes-page/my-processes-page.component.ts (311 bytes)

  update src/app/my-processes/my-processes.module.ts (416 bytes)

 

Martins-MacBook-Pro:my-processes mbergljung$ ng g component my-processes-list-page

  create src/app/my-processes/my-processes-list-page/my-processes-list-page.component.css (0 bytes)

  create src/app/my-processes/my-processes-list-page/my-processes-list-page.component.html (41 bytes)

  create src/app/my-processes/my-processes-list-page/my-processes-list-page.component.spec.ts (722 bytes)

  create src/app/my-processes/my-processes-list-page/my-processes-list-page.component.ts (330 bytes)

  update src/app/my-processes/my-processes.module.ts (552 bytes)

 

Martins-MacBook-Pro:my-processes mbergljung$ ng g component my-processes-details-page

  create src/app/my-processes/my-processes-details-page/my-processes-details-page.component.css (0 bytes)

  create src/app/my-processes/my-processes-details-page/my-processes-details-page.component.html (44 bytes)

  create src/app/my-processes/my-processes-details-page/my-processes-details-page.component.spec.ts (743 bytes)

  create src/app/my-processes/my-processes-details-page/my-processes-details-page.component.ts (342 bytes)

  update src/app/my-processes/my-processes.module.ts (700 bytes)

 

This creates a My Processes module with routing, a My Processes parent page, and a My Processes list page component where we will display the available process instances for the user. It also creates a My Processes details page where we can display more detailed information about a selected process, such as properties, comments, and diagram.

 

We need to set up a new route configuration with a parent route that has two child routes for the list page and details page. Let’s add it in the src/app/my-processes/my-processes-routing.module.ts file as follows:

 

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { MyProcessesListPageComponent } from './my-processes-list-page/my-processes-list-page.component';
import { MyProcessesPageComponent } from './my-processes-page/my-processes-page.component';
import { MyProcessesDetailsPageComponent } from './my-processes-details-page/my-processes-details-page.component';

import { AuthGuardBpm } from 'ng2-alfresco-core';

const routes: Routes = [{
  path: 'my-processes',
  component: MyProcessesPageComponent,
  canActivate: [AuthGuardBpm],
  data: {
    title: 'My Processes',
    icon: 'settings',
    hidden: false,
    needBpmAuth: true,
    isLogin: false
  },
  children: [
    { path: '', component: MyProcessesListPageComponent, canActivate: [AuthGuardBpm] },
    { path: ':process-id', component: MyProcessesDetailsPageComponent, canActivate: [AuthGuardBpm] }
  ]
}];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class MyProcessesRoutingModule { }

 

The parent page route is http://localhost:4200/my-processes, and if we don’t specify any other URL path elements, then the the My Processes List child page will be displayed automatically as it has path ''.

 

We also need to make the new pages and routes 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 { ProcessAppsModule } from './process-apps/process-apps.module';
import { ProcessAppsRoutingModule } from './process-apps/process-apps-routing.module';
import { MyTasksModule } from './my-tasks/my-tasks.module';
import { MyTasksRoutingModule } from './my-tasks/my-tasks-routing.module';
import { MyProcessesModule } from './my-processes/my-processes.module';
import { MyProcessesRoutingModule } from './my-processes/my-processes-routing.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,

    AppCommonModule,
    AppLoginModule,
    AppLoginRoutingModule,
    ProcessAppsModule,
    ProcessAppsRoutingModule,
    MyTasksModule,
    MyTasksRoutingModule,
    MyProcessesModule,
    MyProcessesRoutingModule
  ],
  providers: [AppMenuService],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

 

This section makes heavy use of the ActivitiProcessListModule and Angular Material components. These components are not yet known to the my processes module. Import as follows in the src/app/my-processes/my-processes.module.ts file:

 

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { MyProcessesRoutingModule } from './my-processes-routing.module';
import { MyProcessesPageComponent } from './my-processes-page/my-processes-page.component';
import { MyProcessesListPageComponent } from './my-processes-list-page/my-processes-list-page.component';
import { MyProcessesDetailsPageComponent } from './my-processes-details-page/my-processes-details-page.component';

import { AppCommonModule } from '../app-common/app-common.module';

import { ActivitiProcessListModule } from 'ng2-activiti-processlist';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { DiagramsModule } from 'ng2-activiti-diagrams';

@NgModule({
  imports: [
    CommonModule,
    MyProcessesRoutingModule,

    /* Common App imports (Angular Core and Material, ADF Core */
    AppCommonModule,

    /* ADF libs specific to this module */
    ActivitiProcessListModule,
    ActivitiTaskListModule,
    DiagramsModule
  ],
  declarations: [MyProcessesPageComponent, MyProcessesListPageComponent, MyProcessesDetailsPageComponent]
})
export class MyProcessesModule { }

 

Here we import the ActivitiProcessListModule, which gives us access to Process List and Process Details components. The imported ActivitiTaskListModule is needed as we have to select a Process App before we can see any Process Instances. The DiagramsModule is necessary to import as we are going to show a preview of the Process Instance diagram. At the same time we also import the AppCommonModule that we implemented earlier on, giving us access to ADF Core module and Angular Material components.

 

The output from the my processes list and details pages need to go somewhere. Update the main my processes page template in the src/app/my-processes/my-processes-page/my-processes-page.component.html file as follows, replace whatever markup is already there:

 

<router-outlet></router-outlet>

 

This finishes off the setup of the my processes module.

Implementing the My Processes List component

Now we can move on and implement the My Processes List component that will provide a list of the active process instances that the logged in user has access to. The user will then be able to click on one of the processes and the related details page will be displayed. It will also be possible to switch between running and completed process instances.

 

To do this we are going to use a new ADF component called <adf-process-instance-list that is part of the ng2-activiti-processlist package. This package is already installed so we are ready to start implementing the component.

 

Let’s start with the page template for the process list. Open up the src/app/my-processes/my-processes-list-page/my-processes-list-page.component.html file and replace whatever is there with the following:

 

<adf-toolbar [color]="'accent'">
  <adf-toolbar-title>
    <span *ngIf="appName">My Processes for '{{ appName }}'</span>
    <span *ngIf="!appName">==>> Select an App to see processes</span>
  </adf-toolbar-title>
</adf-toolbar>
<adf-apps
  [layoutType]="'LIST'"
  [filtersAppId]="processAppsFilter"
  (appClick)="onAppClick($event)">

</adf-apps>
<md-divider></md-divider>
<div class="margin10" *ngIf="appName">
  Show processes that are:
  <md-radio-group (change)="onSelectProcessType($event.value)" class="margin10">
    <md-radio-button value="running" checked="true">Running</md-radio-button>
    <md-radio-button value="completed">Completed</md-radio-button>
  </md-radio-group>
</div>
<md-tab-group>
  <md-tab [label]="processTypeName">
    <adf-process-instance-list *ngIf="appId"
                               [appId]="appId"
                               [state]="processState"
                               (rowClick)="onProcessClick($event)">

      <data-columns>
        <data-column key="id" title="Id"></data-column>
        <data-column key="name" title="Name" class="full-width name-column"></data-column>
        <data-column key="started" title="Started" type="date"></data-column>
      </data-columns>
    </adf-process-instance-list>
  </md-tab>
</md-tab-group>

 

We are using familiar ADF components for the toolbar to display the page title. Then, to display the Process Apps, we use the <adf-apps component that we got familiar with in the beginning of this article. After that we got the two radio buttons that allow us to switch between Running and Completed processes for the currently logged in user. 

 

As you can see, we use Angular Material components (i.e. md-) to display the radio button group. These Angular Material components have already been imported and provided via our App Common Module that we created in the beginning. 

 

Now to the new ADF component <adf-process-instance-list, it takes the following properties and event handlers:

 

  • Process query filters:
    • appId: the process application id. Process instances will only be displayed if they belong to this process app.
    • processDefinitionKey: the process definition to match by this key. Process instances will only be displayed if they have been created from this process definition.
    • name: include process only if it matches this name. Name is from ProcessInstance below.
  • state: process instances in this state will be included. Can be running or completed.  
  • sort: sort the process list by this property. For example 'started-desc'. See ProcessInstance below.
  • (rowClick): called when one of the processes in the list is clicked.
  • (onSuccess): called when the Process Instance list has been loaded successfully.
  • (onError): called when the Process Instance list failed to load.

 

Here we describe all the properties and events that can be used with the Process Instance List component. As with all the lists that are based on the ADF Data table it is possible to customise what columns to display. Here we are doing that to be able to show process id, name, and started date. The <data-column key need to match a value in the ProcessInstance class:

 

export class ProcessInstance {
  public businessKey: string;
  public ended: any;
  public graphicalNotationDefined: boolean;
  public id: string;
  public name: string;
  public processDefinitionCategory: string;
  public processDefinitionDeploymentId: string;
  public processDefinitionDescription: string;
  public processDefinitionId: string;
  public processDefinitionKey: string;
  public processDefinitionName: string;
  public processDefinitionVersion: number;
  public startFormDefined: boolean;
  public started: string;
  public startedBy: any;
  public suspended: boolean;
  public tenantId: string;
  public variables: any;

 

The last configuration for the Process List is an event to capture when a process in the list is clicked. The onProcessClick function is then called so we can navigate to the Process Details page. 

 

Note that the radio buttons and the Process Instance list is only displayed when the user selects a Process App.

 

The template is now finished so we can move on and implement the backing component class. Open up the src/app/my-processes/my-processes-list-page/my-processes-list-page.component.ts file and update it as follows:

 

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { AppDefinitionRepresentationModel } from 'ng2-activiti-tasklist';

@Component({
  selector: 'app-my-processes-list-page',
  templateUrl: './my-processes-list-page.component.html',
  styleUrls: ['./my-processes-list-page.component.css']
})
export class MyProcessesListPageComponent implements OnInit {
  appId: number;
  appName: string;
  processTypeName: string;
  processState: string;

  /* Create a filter that will exclude the Task List App and include only new custom apps */
  processAppsFilter = [ { tenantId: 1 } ];

  constructor(private router: Router) {
    this.processTypeName = 'Running';
    this.processState = 'running';
  }

  ngOnInit() {
  }

  onSelectProcessType(displayProcessType: string) {
    console.log('Changed process type to show : ', displayProcessType);

    if (displayProcessType === 'running') {
      this.processTypeName = 'Running';
      this.processState = 'running';
    } else if (displayProcessType === 'completed') {
      this.processTypeName = 'Completed';
      this.processState = 'completed';
    }
  }

  onAppClick(appDef: AppDefinitionRepresentationModel) {
    console.log('Process state: ' , this.processState, ' Selected App : ', appDef);

    if (appDef) {
      this.appId = appDef.id;
      this.appName = appDef.name;
    }
  }

  onProcessClick(id: string) {
    console.log('Navigating to Process Instance Details : ', id);

    this.router.navigate(['/my-processes', id]);
  }
}

 

As usual, we start of by defining the appId, appName, processTypeName, and processState template variables. In the constructor we set up what type of processes we want to display by default, in this case running processes.

 

The onSelectProcessType function is triggered whenever we click on one of the radio buttons. We then set what type of processes to display. The underlying Process List is automatically updated as these are @Input variables.

 

No processes are displayed unless a Process App is selected. The radio buttons are not visible either. When an App is selected the onAppClick function is called from which we set the internal app ID and name.

 

When a process row is clicked the onProcessClick function is called, which will take the user to the process details page.

 

This finishes off the process list page. Let’s move on with the process details page.

Implementing the Process Details component

When the user clicks one of the processes a details page such as the following should be displayed:



 

The page will have an orange toolbar with a title and a close button. Under the toolbar will be a tabbed view of the process details.

 

Let’s start with the details page template, open up the src/app/my-processes/my-processes-details-page/my-processes-details-page.component.html file and update it so it looks like this:

 

<div class="process-details-view">
  <adf-toolbar [color]="'accent'">
    <adf-toolbar-title>
      <span *ngIf="processDetails">Process Details for '{{ processDetails.name }}' ({{ processDetails.id }})</span>
    </adf-toolbar-title>
    <adf-toolbar-divider></adf-toolbar-divider>
    <button md-icon-button
            mdTooltip="Close and go back to task list"
            (click)="onGoBack($event)"
            aria-label="Close">

      <md-icon>close</md-icon>
    </button>
  </adf-toolbar>
  <md-tab-group *ngIf="processDetails">
    <md-tab label="Manage">
      <md-card>
        <span> {{ getProcessNameOrDescription('medium') }}</span>
        <div *ngIf="isRunning()">
          <button md-button type="button" (click)="cancelProcess()">{{ 'DETAILS.BUTTON.CANCEL' | translate }}</button>
        </div>
      </md-card>
    </md-tab>
    <md-tab label="Properties">
      <adf-process-instance-header
        [processInstance]="processDetails">

      </adf-process-instance-header>
    </md-tab>
    <md-tab label="Attachments">
      <adf-process-attachment-list
        [processInstanceId]="processDetails.id"
        (attachmentClick)="onAttachmentClick()">

      </adf-process-attachment-list>
    </md-tab>
    <md-tab label="Comments">
      <adf-process-instance-comments
        [processInstanceId]="processDetails.id"
        [readOnly]="false">

      </adf-process-instance-comments>
    </md-tab>
    <md-tab label="Tasks">
      <adf-process-instance-tasks
        [processInstanceDetails]="processDetails"
        [showRefreshButton]="true">

      </adf-process-instance-tasks>
    </md-tab>
    <md-tab label="Preview">
      <adf-diagram
        [processInstanceId]="processDetails.id">

      </adf-diagram>
    </md-tab>
  </md-tab-group>
</div>

  

This page has the usual ADF Toolbar at the top with process title and identifier. Under the toolbar is an Angular Material tabbed view. Each tab uses an ADF component to display different types of details for the process instance.

 

The first tab, called Manage, presents a process view where you can interact with the Process Instance:

 

 

In this case you can only cancel the process, if it is running. This does not use an ADF component as there is none that can be used to just cancel a process. However, there is a powerful component called <adf-process-instance-details that we don’t use in this application, it could potentially replace all the tabs in this page, and it includes a cancel button. But here we look at each individual feature separately.

 

The second tab is called Properties and displays a lot of the details for the process:

 

 

It is implemented with the <adf-process-instance-header component, which sounds like it would just display a row with for example the process title. But it actually displays a list of relevant process properties. You feed it with an object of type ProcessInstance.

 

The next tab, which is called Attachments, displays any uploaded files that are attached to the process instance, including also files attached to task instances:

 

 

This is done with the <adf-process-attachment-list component. You feed this component with the process instance id. If a process should offer the user the opportunity to upload more files, then you could also use the <adf-create-process-attachment component or configure drag-and-drop via the <adf-upload-drag-area component that is part of the ng2-alfresco-upload package .

 

The following tab, which is called Comments, will display any comments made by users regarding this process instance and any associated task instances:

 

 

We do this with the <adf-process-instance-comments component. You feed this component with the process instance id. If you want the user to be able to add more comments, then set the readOnly property to false.

 

Next tab, which is called Tasks, will display any associated task instances:

 

 

This is done with the <adf-process-instance-tasks component. You feed this component with the process instance.

 

The last tab is called Preview and will show the diagram of a running Process Instance with the activities highlighted according to their state (Active/Completed/Pending):

 

 

This is implemented with the <adf-diagram component. You feed this component with the process instance id.

There is a bug in the diagram component so the activity names are not displayed.

The backing component class is implemented in the src/app/my-processes/my-processes-details-page/my-processes-details-page.component.ts file and looks like this:

 

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { DatePipe } from '@angular/common';

import { ProcessService, ProcessInstance } from 'ng2-activiti-processlist';

@Component({
  selector: 'app-my-processes-details-page',
  templateUrl: './my-processes-details-page.component.html',
  styleUrls: ['./my-processes-details-page.component.css']
})
export class MyProcessesDetailsPageComponent implements OnInit {
  processDetails: ProcessInstance;

  constructor(private router: Router,
              private activatedRoute: ActivatedRoute,
              private processService: ProcessService) {
  }

  ngOnInit() {
    const processId = this.activatedRoute.snapshot.params['process-id'];
    console.log('Showing process details for : ', processId);

    this.processService.getProcess(processId).subscribe(
      (processDetails: ProcessInstance) => {
        this.processDetails = processDetails;
      },
      (error) => {
        console.log('Error: ', error);
      });
  }

  onGoBack($event: Event) {
    this.navigateBack2AppList();
  }

  private navigateBack2AppList() {
    this.router.navigate(['../'],
      {
        relativeTo: this.activatedRoute
      });
  }

  isRunning(): boolean {
    return this.processDetails && !this.processDetails.ended;
  }

  cancelProcess() {
    this.processService.cancelProcess(this.processDetails.id).subscribe(
      (data) => {
        console.log('Process instance was cancelled: ', this.processDetails.id);
        this.processDetails = null;
      }, (err) => {
        console.log('Process instance could not be cancelled: ', this.processDetails.id, ', error: ', err);
      });
  }

  onAttachmentClick() {

  }

  getProcessNameOrDescription(dateFormat): string {
    let name = '';

    if (this.processDetails) {
      name = this.processDetails.name ||
        this.processDetails.processDefinitionName + ' - ' + this.getFormatDate(this.processDetails.started, dateFormat);
    }

    return name;
  }

  private getFormatDate(value, format: string) {
    const datePipe = new DatePipe('en-US');

    try {
      return datePipe.transform(value, format);
    } catch (err) {
      console.log(`Error parsing date ${value} to format ${format}`);
    }
  }

  onTasksError(error: any) {
    console.log(`Error loading tasks ${error} for process instance ${this.processDetails.id}`);
  }
}

 

The component class is very straightforward, it basically just fetches the process instance details for passed in ID. The process-id is passed in via the URL, which looks like http://localhost:4200/my-processes/<process-id>. We extract the process-id in the ngOnInit method via the activedRoute object.

 

When we have the ID we use the ADF ProcessService to fetch a ProcessInstance object representing the details about the process instance. We use the ProcessService also when the user clicks the Cancel process button.

 

We implement the navigate back handler as previously done by just stepping up to the parent route. We don’t need to pass anything back to the process list as it always justy displays all apps and then the user selects an app again.

 

That should do it, you can now try out the process list and process details pages.

 

Summary

To work with version 1.9 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 process management application from scratch in no time, picking the pieces you need.

 

My goal was to build a process management application where I could login and browse the process applications, look at the process definitions, start process instances, interact with task instances and workflow instances, view the details for tasks and processes, and see a preview of the process definitions and instances. 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 ACS side, check out the Building a Content Management App with ADF 1.9.0 article.