Merge pull request #1672 from Alfresco/development

1.2.0
This commit is contained in:
Mario Romano
2017-02-27 15:35:21 +00:00
committed by GitHub
184 changed files with 2498 additions and 1269 deletions
+3 -3
View File
@@ -12,11 +12,11 @@ Please ask before on our gitter channel https://gitter.im/Alfresco/alfresco-ng2-
- [ ] Support request
- [ ] Documentation
```
**Current behavior:**
<!-- Describe the current behavior. -->
**Current behaviour:**
<!-- Describe the current behaviour. -->
**Expected behavior:**
<!-- Describe the expected behavior. -->
<!-- Describe the expected behaviour. -->
**Steps to reproduce the issue:**
<!-- Describe the steps to reproduce the issue. -->
+2 -2
View File
@@ -20,11 +20,11 @@
[ ] Other... Please describe:
```
**What is the current behavior?** (You can also link to an open issue here)
**What is the current behaviour?** (You can also link to an open issue here)
**What is the new behavior?**
**What is the new behaviour?**
+18 -2
View File
@@ -12,7 +12,7 @@
## Installing
To correctly use this demo check that on your machine is running [Node](https://nodejs.org/en/) version 6.9.2 LTS or higher.
To correctly use this demo check that on your machine is running [Node](https://nodejs.org/en/) version 5.x.x or higher.
```sh
git clone https://github.com/Alfresco/alfresco-ng2-components.git
@@ -41,7 +41,7 @@ npm run build
npm run start:dist
```
This command builds broject in `production` mode.
This command builds project in `production` mode.
All output is placed to `dist` folder and can be served your preferred web server.
You should need no additional files outside the `dist` folder.
@@ -81,4 +81,20 @@ Directory structure:
│ └── fr.json
```
## Custom-files
If you need to add custom files on your project you can add this files in the folders public
```
.
├── public/
│ ├── images/
│ ├── css/
│ └── js/
```
the public folder above wil be copied in the root of your project and you can refer to them for example as
* './images/custom_image.png'
* './js/custom_script.js'
* './css/custom_style.css'
-3
View File
@@ -1,3 +0,0 @@
#Windows Installation of *Alfresco Angular 2 Components* project
See https://github.com/Alfresco/alfresco-ng2-components/blob/master/Prerequisites.md
+5 -5
View File
@@ -19,7 +19,7 @@
<nav class="mdl-navigation mdl-layout--large-screen-only">
<a class="mdl-navigation__link" data-automation-id="home" href="" routerLink="/">Home</a>
<a class="mdl-navigation__link" data-automation-id="files" href="" routerLink="/files">DocumentList</a>
<a class="mdl-navigation__link" data-automation-id="activiti" href="" routerLink="/activiti">Activiti</a>
<a class="mdl-navigation__link" data-automation-id="activiti" href="" routerLink="/activiti">Process Services</a>
<a class="mdl-navigation__link" data-automation-id="login" href="" routerLink="/login">Login</a>
<a class="mdl-navigation__link" data-automation-id="settings" href="" routerLink="/settings">Settings</a>
</nav>
@@ -39,14 +39,14 @@
<span class="flag-icon flag-icon-gb"></span>
<label tabindex="0"> English</label>
</a>
<a class="mdl-navigation__link" (click)="changeLanguage('gr')">
<span class="flag-icon flag-icon-gr"></span>
<label tabindex="0"> Greek</label>
</a>
<a class="mdl-navigation__link" (click)="changeLanguage('it')">
<span class="flag-icon flag-icon-it"></span>
<label tabindex="0"> Italian</label>
</a>
<a class="mdl-navigation__link" (click)="changeLanguage('ru')">
<span class="flag-icon flag-icon-ru"></span>
<label tabindex="0"> Russian</label>
</a>
</nav>
<span class="mdl-layout-title">Components</span>
<nav class="mdl-navigation">
+2 -8
View File
@@ -43,13 +43,7 @@ export class AppComponent {
this.setProvider();
if (translateService) {
if (process.env.ENV === 'production') {
translateService.addTranslationFolder('custom', 'i18n/custom-translation');
translateService.addTranslationFolder('ng2-alfresco-login', 'i18n/custom-translation/alfresco-login');
} else {
translateService.addTranslationFolder('custom', 'custom-translation');
translateService.addTranslationFolder('ng2-alfresco-login', 'custom-translation/alfresco-login');
}
translateService.addTranslationFolder('app', 'resources');
}
}
@@ -75,7 +69,7 @@ export class AppComponent {
);
}
navigateToLogin(){
navigateToLogin() {
this.router.navigate(['/login']);
this.hideDrawer();
}
+3 -2
View File
@@ -35,6 +35,7 @@ import { AnalyticsModule } from 'ng2-activiti-analytics';
import { AppComponent } from './app.component';
import { routing } from './app.routes';
import { CustomEditorsModule } from './components/activiti/custom-editor/custom-editor.component';
import { Editor3DModule } from 'ng2-3d-editor';
import {
HomeComponent,
@@ -71,7 +72,8 @@ import {
ActivitiProcessListModule.forRoot(),
UserInfoComponentModule.forRoot(),
AnalyticsModule.forRoot(),
CustomEditorsModule
CustomEditorsModule,
Editor3DModule.forRoot()
],
declarations: [
AppComponent,
@@ -94,4 +96,3 @@ import {
bootstrap: [ AppComponent ]
})
export class AppModule { }
@@ -119,7 +119,10 @@
<activiti-analytics *ngIf="report"
[appId]="appId"
[reportId]="report.id"
(editReport)="onEditReport($event)">
[hideParameters]="false"
(editReport)="onEditReport($event)"
(reportSaved)="onReportSaved()"
(reportDeleted)="onReportDeleted()">
</activiti-analytics>
</div>
</div>
@@ -154,7 +154,7 @@ export class ActivitiDemoComponent implements AfterViewInit {
}
onTaskFilterClick(event: FilterRepresentationModel) {
if(event){
if (event) {
this.taskFilter = event;
}
}
@@ -202,6 +202,21 @@ export class ActivitiDemoComponent implements AfterViewInit {
this.analyticsreportlist.reload();
}
onReportSaved() {
this.analyticsreportlist.reload();
}
onReportDeleted() {
this.analyticsreportlist.reload();
this.selectFirstElementInReportList();
}
selectFirstElementInReportList() {
if (!this.analyticsreportlist.isReportsEmpty()) {
this.analyticsreportlist.selectReport(this.analyticsreportlist.reports[0]);
}
}
navigateStartProcess() {
this.resetProcessFilters();
this.reloadProcessFilters();
@@ -35,6 +35,15 @@
</template>
-->
</content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.TAG' | translate}}"
key="id"
sortable="true"
class="full-width ellipsis-cell">
<template let-entry="$implicit">
<alfresco-tag-node-list [nodeId]="entry.data.getValue(entry.row, entry.col)"></alfresco-tag-node-list>
</template>
</content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
key="createdByUser.displayName"
@@ -165,7 +174,13 @@
<alfresco-viewer [(showViewer)]="fileShowed"
[fileNodeId]="fileNodeId"
[overlayMode]="true">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
<extension-viewer [supportedExtensions]="['obj','3DS']" #extension>
<template let-urlFileContent="urlFileContent" let-extension="extension" >
<threed-viewer [urlFile]="urlFileContent" [extension]="extension" ></threed-viewer>
</template>
</extension-viewer>
</alfresco-viewer>
</div>
@@ -29,6 +29,13 @@
margin-right: 4px;
}
.home--card__text {
font-size: 16px;
line-height: 18px;
}
.home--feature-list {
list-style: none;
padding-left: 0;
@@ -3,11 +3,11 @@
<div class="mdl-card__title mdl-card--expand" routerLink="/files">
<h2 class="mdl-card__title-text">
<i class="material-icons home--card__icon">dvr</i>
<span>DocumentList - ECM</span>
<span class="home--card__text">DocumentList - Content Services</span>
</h2>
</div>
<div class="mdl-card__supporting-text">
Demonstrates multiple Alfresco ECM components used together to display the files of your ECM instance:
Demonstrates multiple Alfresco Content Services components used together to display the files of your Content Services instance:
<ul class="home--feature-list">
<li>
<i class="material-icons home--feature-list__icon">brightness_1</i>
@@ -39,11 +39,11 @@
<div class="mdl-card__title mdl-card--expand" routerLink="/activiti">
<h2 class="mdl-card__title-text">
<i class="material-icons home--card__icon">apps</i>
<span>Activiti - BPM</span>
<span class="home--card__text">Process Services</span>
</h2>
</div>
<div class="mdl-card__supporting-text">
Demonstrates multiple Alfresco BPM components used together to show your BPM process and tasks:
Demonstrates multiple Alfresco Process Services components used together to show your Process Services process and tasks:
<ul class="home--feature-list">
<li>
<i class="material-icons home--feature-list__icon">brightness_1</i>
@@ -91,7 +91,7 @@
<div class="mdl-card__title mdl-card--expand" routerLink="/datatable">
<h2 class="mdl-card__title-text">
<i class="material-icons home--card__icon">view_module</i>
<span>DataTable-ECM&BPM</span>
<span class="home--card__text">DataTable - Content Services & Process Services</span>
</h2>
</div>
<div class="mdl-card__supporting-text">
@@ -111,11 +111,11 @@
<div class="mdl-card__title mdl-card--expand" routerLink="/uploader">
<h2 class="mdl-card__title-text">
<i class="material-icons home--card__icon">file_upload</i>
<span>Uploader - ECM</span>
<span class="home--card__text">Uploader - Content Services</span>
</h2>
</div>
<div class="mdl-card__supporting-text">
Basic table uploader component for the ECM and BPM:
Basic table uploader component for the Content Services & Process Services:
<ul class="home--feature-list">
<li>
<i class="material-icons home--feature-list__icon">brightness_1</i>
@@ -131,11 +131,11 @@
<div class="mdl-card__title mdl-card--expand" routerLink="/login">
<h2 class="mdl-card__title-text">
<i class="material-icons home--card__icon">account_circle</i>
<span>Login - ECM & BPM</span>
<span class="home--card__text">Login - Content Services & Process Services</span>
</h2>
</div>
<div class="mdl-card__supporting-text">
Login component for the ECM and BPM:
Login component for the Content Services and Process Services:
<ul class="home--feature-list">
<li>
<i class="material-icons home--feature-list__icon">brightness_1</i>
@@ -151,11 +151,11 @@
<div class="mdl-card__title mdl-card--expand" routerLink="/webscript">
<h2 class="mdl-card__title-text">
<i class="material-icons home--card__icon">extension</i>
<span>Webscript - ECM</span>
<span class="home--card__text">Webscript - Content Services</span>
</h2>
</div>
<div class="mdl-card__supporting-text">
Displays and creates webscripts in your ECM instance:
Displays and creates webscripts in your Content Services instance:
<ul class="home--feature-list">
<li>
<i class="material-icons home--feature-list__icon">brightness_1</i>
@@ -171,11 +171,11 @@
<div class="mdl-card__title mdl-card--expand" routerLink="/tag">
<h2 class="mdl-card__title-text">
<i class="material-icons home--card__icon">local_offer</i>
<span>Tag - ECM</span>
<span class="home--card__text">Tag - Content Services</span>
</h2>
</div>
<div class="mdl-card__supporting-text">
Displays and adds tags to the node of your ECM instance:
Displays and adds tags to the node of your Content Services instance:
<ul class="home--feature-list">
<li>
<i class="material-icons home--feature-list__icon">brightness_1</i>
@@ -23,6 +23,7 @@
.toggle {
width: 120px;
margin: 20px;
padding: 5px;
}
@media (min-width: 721px) {
@@ -5,14 +5,14 @@
<label for="switch1" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch1" [checked]="isECM" class="mdl-switch__input"
(click)="toggleECM()">
<span class="mdl-switch__label">ECM</span>
<span class="mdl-switch__label">Content Services</span>
</label>
</p>
<p class="toggle">
<label for="switch2" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch2" [checked]="isBPM" class="mdl-switch__input"
(click)="toggleBPM()">
<span class="mdl-switch__label">BPM</span>
<span class="mdl-switch__label">Process Services</span>
</label>
</p>
<p class="toggle">
@@ -49,14 +49,14 @@
<label for="switch1-mobile" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch1-mobile" [checked]="isECM" class="mdl-switch__input"
(click)="toggleECM()">
<span class="mdl-switch__label">ECM</span>
<span class="mdl-switch__label">Content Services</span>
</label>
</p>
<p>
<label for="switch2-mobile" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch2-mobile" [checked]="isBPM" class="mdl-switch__input"
(click)="toggleBPM()">
<span class="mdl-switch__label">BPM</span>
<span class="mdl-switch__label">Process Services</span>
</label>
</p>
<p>
@@ -17,7 +17,6 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Component({
selector: 'search-component',
@@ -7,7 +7,7 @@
</div>
<div class="mdl-card__actions mdl-card--border">
<div class="mdl-card__supporting-text">
ECM host URL configuration
Content Services host URL configuration
</div>
<nav class="mdl-navigation">
<div class="icon material-icons icon-margin">link</div>
@@ -16,7 +16,7 @@
</nav>
<div class="mdl-card__supporting-text">
BPM host URL configuration
Process Services host URL configuration
</div>
<nav class="mdl-navigation">
<div class="icon material-icons icon-margin">link</div>
+1
View File
@@ -62,3 +62,4 @@ if (process.env.ENV === 'production') {
}
require('pdfjs-dist/web/pdf_viewer.js');
require('three/build/three.min.js');
+5 -32
View File
@@ -9,13 +9,13 @@ var CopyWebpackPlugin = require('copy-webpack-plugin');
const rootPath = helpers.root('node_modules');
let pattern = '+(alfresco-js-api|ng2-alfresco|ng2-activiti)*';
let options = {
var pattern = '+(alfresco-js-api|ng2-alfresco|ng2-activiti)*';
var options = {
cwd: rootPath,
realpath: true
};
let alfrescoLibs = glob.sync(pattern, options);
var alfrescoLibs = glob.sync(pattern, options);
// console.dir(alfrescoLibs);
module.exports = {
@@ -159,42 +159,15 @@ module.exports = {
to: 'js/material.min.js',
flatten: true
}, {
context: 'public',
from: 'css/material.orange-blue.min.css',
to: 'css/material.orange-blue.min.css',
flatten: true
}, {
context: 'node_modules',
from: 'material-design-icons/iconfont/',
to: 'css/iconfont/',
flatten: true
}, {
context: 'public',
from: 'js/typedarray.js',
to: 'js/typedarray.js',
flatten: true
}, {
context: 'public',
from: 'js/Blob.js',
to: 'js/Blob.js',
flatten: true
}, {
context: 'public',
from: 'js/formdata.js',
to: 'js/formdata.js',
flatten: true
}, {
context: 'public',
from: 'js/promisePolyfill.js',
to: 'js/promisePolyfill.js',
flatten: true
}, {
context: 'public',
from: 'css/muli-font.css',
to: 'css/muli-font.css',
flatten: true
from: '',
to: ''
}
]),
new webpack.optimize.CommonsChunkPlugin({
+2 -2
View File
@@ -25,9 +25,9 @@ module.exports = webpackMerge(commonConfig, {
to: 'pdf.worker.js'
},
{
context: 'custom-translation',
context: 'resources/i18n',
from: '**/*.json',
to: 'i18n/custom-translation'
to: 'resources/i18n'
},
// Copy i18n folders for all modules with ng2-alfresco- prefix
{
+11 -11
View File
@@ -40,15 +40,15 @@ module.exports = webpackMerge(commonConfig, {
// Reference: http://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
// Minify all javascript, switch loaders to minimizing mode
new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
mangle: {
keep_fnames: true
},
compressor: {
screw_ie8: true,
warnings: false
}
}),
// new webpack.optimize.UglifyJsPlugin({
// mangle: {
// keep_fnames: true
// },
// compressor: {
// screw_ie8: true,
// warnings: false
// }
// }),
// Extract css files
// Reference: https://github.com/webpack/extract-text-webpack-plugin
@@ -66,9 +66,9 @@ module.exports = webpackMerge(commonConfig, {
to: 'pdf.worker.js'
},
{
context: 'custom-translation',
context: 'resources/i18n',
from: '**/*.json',
to: 'i18n/custom-translation'
to: 'resources/i18n'
},
// Copy i18n folders for all modules with ng2-alfresco- prefix
{
+3 -3
View File
@@ -4,13 +4,13 @@ var glob = require('glob');
const rootPath = helpers.root('node_modules');
let pattern = '+(alfresco-js-api|ng2-alfresco|ng2-activiti)*';
let options = {
var pattern = '+(alfresco-js-api|ng2-alfresco|ng2-activiti)*';
var options = {
cwd: rootPath,
realpath: true
};
let alfrescoLibs = glob.sync(pattern, options);
var alfrescoLibs = glob.sync(pattern, options);
module.exports = {
devtool: 'inline-source-map',
@@ -1,29 +0,0 @@
{
"LOGIN": {
"LOGO": "Alfresco",
"LABEL": {
"LOGIN": "Login",
"USERNAME": "Username",
"PASSWORD": "Password",
"REMEMBER": "Remember"
},
"MESSAGES": {
"USERNAME-REQUIRED": "Required",
"USERNAME-MIN": "Your username needs to be at least {{customMinLenght}} characters.",
"PASSWORD-REQUIRED": "Enter your password to sign in",
"LOGIN-ERROR-CREDENTIALS": "You have entered an invalid username or password",
"LOGIN-ERROR-PROVIDERS": "Providers cannot be undefined",
"LOGIN-SUCCESS": "Login successful",
"LOGIN-ERROR-CORS": "CORS exception, check your server configuration",
"LOGIN-ERROR-CSRF": "CSRF exception, set [disableCsrf]=\"true\" in alfresco-login component",
"LOGIN-ECM-LICENSE": "The ECM is in read-only mode"
},
"BUTTON": {
"LOGIN": "SIGN IN"
},
"ACTION": {
"HELP": "NEED HELP?",
"REGISTER": "REGISTER"
}
}
}
@@ -1,28 +0,0 @@
{
"LOGIN": {
"LABEL": {
"LOGIN": "Autenticazione",
"USERNAME": "Nome utente",
"PASSWORD": "Password",
"REMEMBER": "Ricordami"
},
"MESSAGES": {
"USERNAME-REQUIRED": "Campo obbligatorio",
"USERNAME-MIN": "Inserire un nome utente di minimo 4 caratteri.",
"PASSWORD-REQUIRED": "Campo obbligatorio",
"LOGIN-ERROR-CREDENTIALS": "Nome utente o password non validi",
"LOGIN-ERROR-PROVIDERS": "Providers non può essere nullo",
"LOGIN-SUCCESS": "Login effettuata con successo",
"LOGIN-ERROR-CORS": "CORS exception, controlla le configurazioni del tuo server",
"LOGIN-ERROR-CSRF": "CSRF exception, set [disableCsrf]=\"true\" in alfresco-login component",
"LOGIN-ECM-LICENSE": "l'ECM e' in read-only mode"
},
"BUTTON": {
"LOGIN": "Accedi"
},
"ACTION": {
"HELP": "BISOGNO DI AIUTO?",
"REGISTER": "REGISTRATI"
}
}
}
+21 -20
View File
@@ -1,7 +1,7 @@
{
"name": "Alfresco-Angular2-Demo",
"description": "Demo shell for Alfresco Angular2 components",
"version": "1.1.0",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings dist",
@@ -9,10 +9,10 @@
"start:dist": "wsrv -s dist/ -p 3000 -a 0.0.0.0",
"clean-build": "rimraf 'app/{,**/}**.js' 'app/{,**/}**.js.map' 'app/{,**/}**.d.ts'",
"test": "karma start",
"build": "npm run server-versions && rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail",
"build": "npm run tslint && npm run server-versions && rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail",
"server-versions": "rimraf versions.json && npm list --depth=0 --json=true --prod=true > versions.json || exit 0",
"aws": "node app.js",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'app/{,**/}**.ts'",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'app/{,**/}**.ts' -e 'app/vendor.ts' -e 'app/polyfills.ts' ",
"licensecheck": "license-check"
},
"repository": {
@@ -78,27 +78,28 @@
"ng2-charts": "1.1.0",
"raphael": "^2.2.6",
"md-date-time-picker": "^2.2.0",
"alfresco-js-api": "~1.1.0",
"ng2-activiti-analytics": "1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-alfresco-datatable": "1.1.0",
"ng2-alfresco-documentlist": "1.1.0",
"ng2-alfresco-login": "1.1.0",
"ng2-alfresco-search": "1.1.0",
"ng2-alfresco-upload": "1.1.0",
"ng2-alfresco-viewer": "1.1.0",
"ng2-activiti-form": "1.1.0",
"ng2-activiti-tasklist": "1.1.0",
"ng2-alfresco-userinfo": "1.1.0",
"ng2-activiti-processlist": "1.1.0",
"ng2-alfresco-webscript": "1.1.0",
"ng2-alfresco-tag": "1.1.0",
"alfresco-js-api": "~1.2.0",
"ng2-3d-editor": "0.0.15",
"ng2-activiti-analytics": "1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-alfresco-datatable": "1.2.0",
"ng2-alfresco-documentlist": "1.2.0",
"ng2-alfresco-login": "1.2.0",
"ng2-alfresco-search": "1.2.0",
"ng2-alfresco-upload": "1.2.0",
"ng2-alfresco-viewer": "1.2.0",
"ng2-activiti-form": "1.2.0",
"ng2-activiti-tasklist": "1.2.0",
"ng2-alfresco-userinfo": "1.2.0",
"ng2-activiti-processlist": "1.2.0",
"ng2-alfresco-webscript": "1.2.0",
"ng2-alfresco-tag": "1.2.0",
"dialog-polyfill": "^0.4.3",
"element.scrollintoviewifneeded-polyfill": "^1.0.1"
},
"devDependencies": {
"@types/jasmine": "^2.5.35",
"@types/node": "^6.0.45",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"angular2-template-loader": "^0.6.0",
"awesome-typescript-loader": "^2.2.4",
"classlist-polyfill": "^1.0.3",
@@ -4,6 +4,7 @@
"DOCUMENT_LIST": {
"COLUMNS": {
"DISPLAY_NAME": "Display name",
"TAG": "Tag",
"CREATED_BY": "Created by",
"CREATED_ON": "Created on"
},
@@ -4,6 +4,7 @@
"DOCUMENT_LIST": {
"COLUMNS": {
"DISPLAY_NAME": "Mostra name",
"TAG": "Tag",
"CREATED_BY": "Creato da",
"CREATED_ON": "Creato il"
},
+4 -6
View File
@@ -3,7 +3,6 @@
"align": [
true,
"parameters",
"arguments",
"statements"
],
"ban": false,
@@ -29,15 +28,14 @@
],
"member-ordering": [
true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"no-any": false,
"no-arg": true,
"no-bitwise": true,
"no-bitwise": false,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": false,
"no-consecutive-blank-lines": true,
"no-console": [
true,
"debug",
@@ -51,7 +49,7 @@
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
@@ -64,7 +62,7 @@
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"no-var-requires": false,
"no-var-requires": true,
"object-literal-sort-keys": false,
"one-line": [
true,
@@ -104,15 +104,14 @@ Follow the 3 steps below:
- ng2-activiti-diagrams
- ng2-activiti-analytics
Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) .
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## Basic usage example Activiti Analytics List
The component shows the list of all the available reports
```html
<analytics-report-list></analytics-report-list>
<analytics-report-list [layoutType]="'LIST'"></analytics-report-list>
```
Usage example of this component :
@@ -132,7 +131,7 @@ import { AnalyticsModule } from 'ng2-activiti-analytics';
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--8-col task-column mdl-shadow--2dp">
<analytics-report-list></analytics-report-list>
<analytics-report-list [layoutType]="'LIST'"></analytics-report-list>
</div>
</div>
</div>`
@@ -179,7 +178,9 @@ platformBrowserDynamic().bootstrapModule(AppModule);
#### Options
No options.
| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `layoutType` | {string} | required | Define the layout of the apps. There are two possible values: GRID or LIST. LIST is the default value|
## Basic usage example Activiti Analytics
@@ -206,7 +207,7 @@ import { AnalyticsModule } from 'ng2-activiti-analytics';
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--8-col task-column mdl-shadow--2dp">
<activiti-analytics [appId]="1001" [reportId]="2006"></activiti-analytics>
<activiti-analytics [appId]="1001" [reportId]="2006" [hideParameters]="false"></activiti-analytics>
</div>
</div>
</div>`
@@ -249,6 +250,8 @@ platformBrowserDynamic().bootstrapModule(AppModule);
| --- | --- |
|`onSuccess` | The event is emitted when the report parameters are loaded |
|`onError` | The event is emitted when an error occur during the loading |
|`reportSaved` | The event is emitted when a report is saved |
|`reportDeleted` | The event is emitted when a report is deleted |
#### Options
@@ -256,8 +259,40 @@ platformBrowserDynamic().bootstrapModule(AppModule);
| --- | --- |
|`appId` | The application id |
|`reportId` | The report id |
|`hideParameters` | Boolean to hide or show the analytics parameters |
|`debug` | Flag to enable or disable the Form values in the console log |
You can also use the activiti analytic component to show straight away the charts without show the parameters setting the hideParameters to true
```html
<activiti-analytics [appId]="appId" [reportId]="reportId" [hideParameters]="true"></activiti-analytics>
```
![Analytics-without-parameters](docs/assets/analytics-without-parameters.png)
## Basic usage example Analytics Generator
The component generate and show the charts
```html
<activiti-analytics-generator [reportId]="reportId" [reportParamQuery]="reportParamQuery"></activiti-analytics>
```
#### Events
| Name | Description |
| --- | --- |
|`onSuccess` | The event is emitted when the charts are loaded |
|`onError` | The event is emitted when an error occur during the loading |
#### Options
| Name | Description |
| --- | --- |
|`reportId` | The report id |
|`reportParamQuery` | The object contains all the parameters that the report needs |
## Build from sources
Alternatively you can build component from sources with the following commands:
@@ -12,6 +12,7 @@
"server": "wsrv -o -s -l",
"build": "npm run tslint && npm run clean-build && npm run tsc",
"build:w": "npm run tslint && rimraf dist && npm run tsc:w",
"travis": "npm link ng2-alfresco-core ng2-activiti-diagrams",
"tsc": "tsc",
"tsc:w": "tsc -w",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts -e '{,**/}**.d.ts'"
@@ -58,10 +59,10 @@
"moment": "2.15.1",
"raphael": "^2.2.6",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-activiti-diagrams": "1.1.0",
"ng2-activiti-analytics": "^1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-activiti-diagrams": "1.2.0",
"ng2-activiti-analytics": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

@@ -1,7 +1,7 @@
{
"name": "ng2-activiti-analytics",
"description": "Activiti Angular2 Analytics Component",
"version": "1.1.0",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
@@ -62,14 +62,14 @@
"ng2-charts": "1.1.0",
"moment": "2.15.1",
"raphael": "^2.2.6",
"alfresco-js-api": "~1.1.0",
"alfresco-js-api": "~1.2.0",
"ng2-translate": "2.5.0",
"ng2-alfresco-core": "1.1.0",
"ng2-activiti-diagrams": "1.1.0"
"ng2-alfresco-core": "1.2.0",
"ng2-activiti-diagrams": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"autoprefixer": "^6.5.4",
"concurrently": "^2.2.0",
"cpx": "1.3.1",
@@ -139,6 +139,13 @@ export var reportDefParamTask = {
'definition': '{ "parameters" :[{"id":"taskName","name":null,"nameKey":null,"type":"task","value":null,"dependsOn":"processDefinitionId"}]}'
};
export var reportNoParameterDefinitions = {
'id': 2006,
'name': 'Fake Task service level agreement',
'created': '2016-10-05T15:39:40.222+0000',
'definition': '{ "parameters" : []}'
};
export var reportDefParamTaskOptions = ['Fake task name 1', 'Fake task name 2'];
export var fieldProcessDef = new ReportParameterDetailsModel(
@@ -3,3 +3,8 @@
.analytics-row__entry {
cursor: pointer;
}
.report-icons {
margin: 20px 20px 20px 20px;
float: right;
}
@@ -1,9 +1,18 @@
<div *ngIf="reports">
<div *ngFor="let report of reports">
<h4>{{report.title}}</h4>
<div class="report-icons">
<button mdTooltip="{{report.title}}" (click)="selectCurrent(idx)"
[class.mdl-button--accent]="isCurrent(idx)"
class="mdl-button mdl-js-button"
*ngFor="let report of reports; let idx = index">
<i class="material-icons">{{report.icon}}</i>
</button>
</div>
<div style="clear: both"> </div>
<div *ngFor="let report of reports; let idx = index">
<div [ngSwitch]="report.type">
<div *ngSwitchCase="'pie'">
<div class="col-md-6">
<div class="col-md-6" *ngIf="isCurrent(idx)">
<h4>{{report.title}}</h4>
<div *ngIf="!report.hasData()">{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}</div>
<div *ngIf="report.hasData()">
<div *ngIf="report.hasZeroValues()">{{'ANALYTICS.MESSAGES.ZERO-DATA-FOUND' | translate}}</div>
@@ -14,44 +23,51 @@
</div>
</div>
</div>
<div *ngSwitchCase="'table'">
<div *ngIf="!report.hasDatasets()">{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}</div>
<div [attr.id]="'chart-table-' + report.id" *ngIf="report.hasDatasets()">
<table class="table table-responsive table-condensed" style="width: 100%">
<tr>
<th *ngFor="let label of report.labels">{{label | translate}}</th>
</tr>
<tr *ngFor="let rows of report.datasets" style="text-align: center;">
<td *ngFor="let row of rows">{{row | translate }}</td>
</tr>
</table>
<div *ngSwitchCase="'table'" >
<div *ngIf="isCurrent(idx)">
<h4>{{report.title}}</h4>
<div *ngIf="!report.hasDatasets()">{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}</div>
<div [attr.id]="'chart-table-' + report.id" *ngIf="report.hasDatasets()">
<table class="table table-responsive table-condensed" style="width: 80%;margin-left: 20px">
<tr>
<th *ngFor="let label of report.labels">{{label | translate}}</th>
</tr>
<tr *ngFor="let rows of report.datasets">
<td *ngFor="let row of rows">{{row | translate }}</td>
</tr>
</table>
</div>
</div>
</div>
<div *ngSwitchCase="'masterDetailTable'">
<div *ngIf="!report.hasDatasets()">{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}</div>
<div [attr.id]="'chart-master-detail-table-' + report.id" *ngIf="report.hasDatasets()">
<table class="table table-responsive table-condensed" style="width: 100%">
<tr>
<th *ngFor="let label of report.labels">{{label | translate}}</th>
</tr>
<tr *ngFor="let rows of report.datasets" class="analytics-row__entry" style="text-align: center;">
<td *ngFor="let row of rows" (click)="toggleDetailsTable()">{{row | translate }}</td>
</tr>
</table>
</div>
<div [attr.id]="'chart-master-detail-' + report.id" *ngIf="isShowDetails()">
<table class="table table-responsive table-condensed" style="width: 100%">
<tr>
<th *ngFor="let label of report.detailsTable.labels">{{label | translate}}</th>
</tr>
<tr *ngFor="let rows of report.detailsTable.datasets" style="text-align: center;">
<td *ngFor="let row of rows">{{row | translate }}</td>
</tr>
</table>
<div *ngSwitchCase="'masterDetailTable'" >
<div *ngIf="isCurrent(idx)">
<h4>{{report.title}}</h4>
<div *ngIf="!report.hasDatasets()">{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}</div>
<div [attr.id]="'chart-master-detail-table-' + report.id" *ngIf="report.hasDatasets()">
<table class="table table-responsive table-condensed" style="width: 100%">
<tr>
<th *ngFor="let label of report.labels">{{label | translate}}</th>
</tr>
<tr *ngFor="let rows of report.datasets" class="analytics-row__entry">
<td *ngFor="let row of rows" (click)="toggleDetailsTable()">{{row | translate }}</td>
</tr>
</table>
</div>
<div [attr.id]="'chart-master-detail-' + report.id" *ngIf="isShowDetails()">
<table class="table table-responsive table-condensed" style="width: 100%">
<tr>
<th *ngFor="let label of report.detailsTable.labels">{{label | translate}}</th>
</tr>
<tr *ngFor="let rows of report.detailsTable.datasets">
<td *ngFor="let row of rows">{{row | translate }}</td>
</tr>
</table>
</div>
</div>
</div>
<div *ngSwitchCase="'bar'">
<div class="col-md-6">
<div class="col-md-6" *ngIf="isCurrent(idx)">
<h4>{{report.title}}</h4>
<div *ngIf="!report.hasDatasets()">{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}</div>
<base-chart *ngIf="report.hasDatasets()" class="chart"
[datasets]="report.datasets"
@@ -61,7 +77,8 @@
</div>
</div>
<div *ngSwitchCase="'multiBar'">
<div class="col-md-6">
<div class="col-md-6" *ngIf="isCurrent(idx)">
<h4>{{report.title}}</h4>
<div *ngIf="!report.hasDatasets()">{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}</div>
<div *ngIf="report.hasDatasets()">
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" [attr.for]="'stacked-id'">
@@ -80,7 +97,10 @@
</div>
</div>
<div *ngSwitchCase="'HeatMap'">
<analytics-report-heat-map [report]="report"></analytics-report-heat-map>
<div *ngIf="isCurrent(idx)">
<h4>{{report.title}}</h4>
<analytics-report-heat-map [report]="report"></analytics-report-heat-map>
</div>
</div>
<div *ngSwitchDefault>
<span>{{'ANALYTICS.MESSAGES.UNKNOWN-WIDGET-TYPE' | translate}}: {{report.type}}</span>
@@ -135,6 +135,44 @@ describe('AnalyticsGeneratorComponent', () => {
});
});
it('Should render the Process definition overview report when onchanges is called ', (done) => {
component.onSuccess.subscribe((res) => {
expect(res).toBeDefined();
expect(res.length).toEqual(3);
expect(res[0]).toBeDefined();
expect(res[0].type).toEqual('table');
expect(res[0].datasets).toBeDefined();
expect(res[0].datasets.length).toEqual(4);
expect(res[0].datasets[0][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-TOTAL-PROCESS-DEFINITIONS');
expect(res[0].datasets[0][1]).toEqual('9');
expect(res[0].datasets[1][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-TOTAL-PROCESS-INSTANCES');
expect(res[0].datasets[1][1]).toEqual('41');
expect(res[0].datasets[2][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-ACTIVE-PROCESS-INSTANCES');
expect(res[0].datasets[2][1]).toEqual('3');
expect(res[0].datasets[3][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-COMPLETED-PROCESS-INSTANCES');
expect(res[0].datasets[3][1]).toEqual('38');
expect(res[1]).toBeDefined();
expect(res[1].type).toEqual('pie');
expect(res[2]).toBeDefined();
expect(res[2].type).toEqual('table');
done();
});
component.reportId = 1001;
component.reportParamQuery = new ReportQuery({status: 'All'});
component.ngOnChanges();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: analyticMock.chartProcessDefOverview
});
});
it('Should render the Task overview report ', (done) => {
component.onSuccess.subscribe((res) => {
expect(res).toBeDefined();
@@ -44,6 +44,7 @@ export class AnalyticsGeneratorComponent implements OnChanges {
reports: Chart[];
showDetails: boolean = false;
currentChartPosition: number;
public barChartOptions: any = {
responsive: true,
@@ -80,9 +81,15 @@ export class AnalyticsGeneratorComponent implements OnChanges {
}
public generateReport(reportId, reportParamQuery) {
if (reportParamQuery === undefined || reportParamQuery === null) {
reportParamQuery = {};
}
this.analyticsService.getReportsByParams(reportId, reportParamQuery).subscribe(
(res: Chart[]) => {
this.reports = res;
if (this.reports) {
this.selectFirstReport();
}
this.onSuccess.emit(res);
},
(err: any) => {
@@ -116,4 +123,16 @@ export class AnalyticsGeneratorComponent implements OnChanges {
isShowDetails(): boolean {
return this.showDetails;
}
isCurrent(position: number) {
return position === this.currentChartPosition ? true : false;
}
selectCurrent(position: number) {
this.currentChartPosition = position;
}
selectFirstReport() {
this.selectCurrent(0);
}
}
@@ -17,3 +17,36 @@
.activiti-filters__entry.active .activiti-filters__entry-icon {
color: rgb(68,138,255);
}
.application-title {
color: white;
z-index: 7;
}
.logo {
position: absolute;
right: 20px;
top: 35px;
z-index: 6;
}
.logo i{
font-size: 70px;
}
.theme-1 {
background-color: #269abc;
}
.theme-1 .logo i {
color: #168aac;
}
.theme-1 .mdl-card__actions i {
color: #168aac;
}
.theme-1 .mdl-card__actions i:hover {
color: #b7dfea;
}
.selectedIcon{
color: #e9f1f3!important;
}
@@ -1,5 +1,5 @@
<div class="menu-container">
<ul class='mdl-list'>
<ul class='mdl-list' *ngIf="isList()">
<li class="mdl-list__item activiti-filters__entry" (click)="selectReport(report)" *ngFor="let report of reports; let idx = index"
[class.active]="currentReport === report">
<span [attr.id]="'report-list-' + idx" class="mdl-list__item-primary-content">
@@ -8,4 +8,18 @@
</span>
</li>
</ul>
<div class="mdl-grid" *ngIf="isGrid()">
<div (click)="selectReport(report)" [ngClass]="['mdl-card mdl-cell', 'theme-1']" *ngFor="let report of reports;">
<div class="logo"><i class="material-icons">equalizer</i></div>
<div class="mdl-card__title">
<h1 class="mdl-card__title-text application-title">{{report.name}}</h1>
</div>
<div class="mdl-card__supporting-text">
<p>{{report.description}}</p>
</div>
<div class="mdl-card__actions mdl-card--border">
<i class="material-icons selectedIcon" *ngIf="isSelected(report)">done</i>
</div>
</div>
</div>
</div>
@@ -21,6 +21,7 @@ import { Observable } from 'rxjs/Rx';
import { CoreModule, AlfrescoTranslationService } from 'ng2-alfresco-core';
import { AnalyticsReportListComponent } from '../components/analytics-report-list.component';
import { AnalyticsService } from '../services/analytics.service';
import { ReportParametersModel } from '../models/report.model';
declare let jasmine: any;
@@ -166,6 +167,59 @@ describe('AnalyticsReportListComponent', () => {
component.selectReport(reportSelected);
});
it('Should return true if the current report is selected', () => {
component.selectReport(reportSelected);
expect(component.isSelected(reportSelected)).toBe(true);
});
it('Should return false if the current report is different', () => {
component.selectReport(reportSelected);
let anotherReport = {'id': 111, 'name': 'Another Fake Test Process definition overview'};
expect(component.isSelected(anotherReport)).toBe(false);
});
it('Should reload the report list', (done) => {
component.initObserver();
let report = new ReportParametersModel({'id': 2002, 'name': 'Fake Test Process definition heat map'});
component.reports = [report];
expect(component.reports.length).toEqual(1);
component.reload();
component.onSuccess.subscribe(() => {
expect(component.reports.length).toEqual(5);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: reportList
});
});
});
describe('layout', () => {
it('should display a list by default', () => {
fixture.detectChanges();
expect(component.isGrid()).toBe(false);
expect(component.isList()).toBe(true);
});
it('should display a grid when configured to', () => {
component.layoutType = AnalyticsReportListComponent.LAYOUT_GRID;
fixture.detectChanges();
expect(component.isGrid()).toBe(true);
expect(component.isList()).toBe(false);
});
it('should display a list when configured to', () => {
component.layoutType = AnalyticsReportListComponent.LAYOUT_LIST;
fixture.detectChanges();
expect(component.isGrid()).toBe(false);
expect(component.isList()).toBe(true);
});
});
});
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, OnInit, Output, Input } from '@angular/core';
import { Observer, Observable } from 'rxjs/Rx';
import { LogService } from 'ng2-alfresco-core';
import { AnalyticsService } from '../services/analytics.service';
@@ -29,6 +29,12 @@ import { ReportParametersModel } from '../models/report.model';
})
export class AnalyticsReportListComponent implements OnInit {
public static LAYOUT_LIST: string = 'LIST';
public static LAYOUT_GRID: string = 'GRID';
@Input()
layoutType: string = AnalyticsReportListComponent.LAYOUT_LIST;
@Output()
reportClick: EventEmitter<ReportParametersModel> = new EventEmitter<ReportParametersModel>();
@@ -51,11 +57,15 @@ export class AnalyticsReportListComponent implements OnInit {
}
ngOnInit() {
this.initObserver();
this.getReportList();
}
initObserver() {
this.report$.subscribe((report: ReportParametersModel) => {
this.reports.push(report);
});
this.getReportList();
}
/**
@@ -131,4 +141,16 @@ export class AnalyticsReportListComponent implements OnInit {
this.currentReport = report;
this.reportClick.emit(report);
}
isSelected(report: any) {
return this.currentReport === report ? true : false;
}
isList() {
return this.layoutType === AnalyticsReportListComponent.LAYOUT_LIST;
}
isGrid() {
return this.layoutType === AnalyticsReportListComponent.LAYOUT_GRID;
}
}
@@ -56,10 +56,50 @@
}
.report-container {
border: solid 1px rgb(212, 212, 212);
border-bottom: solid 1px rgb(212, 212, 212);
padding: 10px 10px 10px 10px;
}
.report-container-setting {
padding-left: 10px;
}
.option_button_details{
padding-top: 20px;
}
.report-icons {
float: right;
}
.report-icons button {
min-width: 48px;
padding: 0 0px;
}
.mdl-dialog__title.choose_name{
padding: 0px;
}
.mdl-dialog.options-name-dialog {
width: 30%;
}
.export-message{
background-color: lightgray;
}
.save-export-input{
width:100%;
}
.delete-parameter {
position: absolute;
margin-left: 60%;
padding-top: 5px;
}
.hide {
display: none;
}
@@ -1,83 +1,117 @@
<div class="report-container">
<a class="mdl-navigation__link setting-button" data-automation-id="settings">
<button (click)="toggleParameters()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
<i class="material-icons">settings</i>
</button>
<span class="report-container-setting">{{'ANALYTICS.MESSAGES.SETTING-TITLE' | translate}}</span>
</a>
<div class="col-md-6" [class.is-hide]="isParametersHide()" >
<div *ngIf="reportParameters">
<form [formGroup]="reportForm" novalidate>
<div *ngIf="isEditable">
<input
type="text"
class="mdl-textfield__input large"
id="reportName"
autofocus
data-automation-id="reportName"
[value]="reportParameters.name"
(input)="reportParameters.name=$event.target.value"
(blur)="editTitle($event)"
<div [class.hide]="hideComponent">
<div class="report-container">
<div class="col-md-6">
<div *ngIf="reportParameters">
<form [formGroup]="reportForm" novalidate>
<div *ngIf="isEditable">
<input
type="text"
class="mdl-textfield__input large"
id="reportName"
autofocus
data-automation-id="reportName"
[value]="reportParameters.name"
(input)="reportParameters.name=$event.target.value"
(blur)="editTitle($event)"
/>
</div>
<div *ngIf="!isEditable">
<div class="report-icons">
<button mdTooltip="{{'ANALYTICS.MESSAGES.ICON-SETTING' | translate}}" (click)="toggleParameters()" class="mdl-button mdl-js-button">
<i class="material-icons">settings</i>
</button>
<button id="delete-button" (click)="deleteReport(reportId)" mdTooltip="{{'ANALYTICS.MESSAGES.ICON-DELETE' | translate}}" class="mdl-button mdl-js-button">
<i class="material-icons">delete</i>
</button>
<span *ngIf="isFormValid()">
<button id="export-button" (click)="showDialog('Export')" mdTooltip="{{'ANALYTICS.MESSAGES.ICON-EXPORT-CSV' | translate}}" class="mdl-button mdl-js-button">
<i class="material-icons">file_download</i>
</button>
<button id="save-button" (click)="showDialog('Save')" mdTooltip="{{'ANALYTICS.MESSAGES.ICON-SAVE' | translate}}" class="mdl-button mdl-js-button">
<i class="material-icons">save</i>
</button>
</span>
</div>
<div class="icon-small">
<i class="material-icons">mode_edit</i>
<h4 (click)="editEnable()">{{reportParameters.name}}</h4>
</div>
</div>
<div *ngFor="let field of reportParameters.definition.parameters" [class.is-hide]="isParametersHide()">
<div [ngSwitch]="field.type">
<div *ngSwitchCase="'integer'">
<br>
<number-widget [field]="field" [group]="reportForm.controls.processInstanceGroup" [controllerName]="'slowProcessInstanceInteger'"
[required]="true"></number-widget>
</div>
<div *ngSwitchCase="'duration'">
<br>
<duration-widget [field]="field" [group]="reportForm.controls.durationGroup"
[controllerName]="'duration'"></duration-widget>
</div>
<div *ngSwitchCase="'boolean'">
<br>
<checkbox-widget [field]="field" [group]="reportForm.controls.typeFilteringGroup"
[controllerName]="'typeFiltering'"></checkbox-widget>
</div>
<div *ngSwitchCase="'status'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.statusGroup" [controllerName]="'status'"
[required]="true"></dropdown-widget>
</div>
<div *ngSwitchCase="'processDefinition'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.processDefGroup" [controllerName]="'processDefinitionId'"
[required]="true" (fieldChanged)="onProcessDefinitionChanges(field)"></dropdown-widget>
</div>
<div *ngSwitchCase="'task'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.taskGroup" [controllerName]="'taskName'"
[required]="true"></dropdown-widget>
</div>
<div *ngSwitchCase="'dateRange'">
<br>
<date-range-widget [field]="field" [group]="reportForm.controls.dateRange"></date-range-widget>
</div>
<div *ngSwitchCase="'dateInterval'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.dateIntervalGroup" [controllerName]="'dateRangeInterval'"
[required]="true" [showDefaultOption]="false"></dropdown-widget>
</div>
<div *ngSwitchDefault>
<span>{{'ANALYTICS.MESSAGES.UNKNOWN-WIDGET-TYPE' | translate}}: {{field.type}}</span>
</div>
</div>
</div>
<dialog id="report-dialog" class="mdl-dialog options-name-dialog" #reportNameDialog>
<h5 id="report-dialog-title" class="mdl-dialog__title">{{action}} report</h5>
<div class="mdl-dialog__content">
<div *ngIf="isSaveAction()" id="save-title-submessage" class="export-message">{{'DIALOG.SAVE_MESSAGE' | translate}}</div>
<div class="mdl-textfield mdl-js-textfield save-export-input">
<label id="report-name-label" [attr.for]="repname">Report Name</label>
<input class="mdl-textfield__input"
type="text"
id="repName"
[attr.value]="reportName"
[(ngModel)]="reportName"
[ngModelOptions]="{standalone: true}"
placeholder="report name">
</div>
</div>
<div class="mdl-dialog__actions">
<button type="button" id="close-dialog-button" (click)="closeDialog()" class="mdl-button close">Close</button>
<button type="button" id="action-dialog-button" (click)="performAction(action, reportParamQuery)"
class="mdl-button close">{{action}}
</button>
</div>
</dialog>
<div *ngIf="debug">
<p>ReportForm valid : {{ reportForm.valid }}</p>
<p>ReportForm status : {{ reportForm.errors | json }}</p>
<p>ReportForm FormGroup valid : {{reportForm && reportForm.controls.dateRange.valid | json }}</p>
</div>
</form>
</div>
<div *ngIf="!isEditable">
<span class="icon-small">
<i class="material-icons">mode_edit</i>
<h4 (click)="editEnable()">{{reportParameters.name}}</h4>
</span>
</div><hr>
<div *ngFor="let field of reportParameters.definition.parameters">
<div [ngSwitch]="field.type">
<div *ngSwitchCase="'integer'">
<br>
<number-widget [field]="field" [group]="reportForm.controls.processInstanceGroup" [controllerName]="'slowProcessInstanceInteger'"
[required]="true"></number-widget>
</div>
<div *ngSwitchCase="'duration'">
<br>
<duration-widget [field]="field" [group]="reportForm.controls.durationGroup"
[controllerName]="'duration'"></duration-widget>
</div>
<div *ngSwitchCase="'boolean'">
<br>
<checkbox-widget [field]="field" [group]="reportForm.controls.typeFilteringGroup"
[controllerName]="'typeFiltering'"></checkbox-widget>
</div>
<div *ngSwitchCase="'status'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.statusGroup" [controllerName]="'status'"
[required]="true"></dropdown-widget>
</div>
<div *ngSwitchCase="'processDefinition'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.processDefGroup" [controllerName]="'processDefinitionId'"
[required]="true" (fieldChanged)="onProcessDefinitionChanges(field)"></dropdown-widget>
</div>
<div *ngSwitchCase="'task'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.taskGroup" [controllerName]="'taskName'"
[required]="true"></dropdown-widget>
</div>
<div *ngSwitchCase="'dateRange'">
<br>
<date-range-widget [field]="field" [group]="reportForm.controls.dateRange"></date-range-widget>
</div>
<div *ngSwitchCase="'dateInterval'">
<br>
<dropdown-widget [field]="field" [group]="reportForm.controls.dateIntervalGroup" [controllerName]="'dateRangeInterval'"
[required]="true" [showDefaultOption]="false"></dropdown-widget>
</div>
<div *ngSwitchDefault>
<span>{{'ANALYTICS.MESSAGES.UNKNOWN-WIDGET-TYPE' | translate}}: {{field.type}}</span>
</div>
</div>
</div>
<div *ngIf="debug">
<p>ReportForm valid : {{ reportForm.valid }}</p>
<p>ReportForm status : {{ reportForm.errors | json }}</p>
<p>ReportForm FormGroup valid : {{reportForm && reportForm.controls.dateRange.valid | json }}</p>
</div>
</form>
</div>
</div>
</div>
@@ -55,7 +55,9 @@ describe('AnalyticsReportParametersComponent', () => {
let translateService = TestBed.get(AlfrescoTranslationService);
spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => { return Observable.of(key); });
spyOn(translateService, 'get').and.callFake((key) => {
return Observable.of(key);
});
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
@@ -80,17 +82,22 @@ describe('AnalyticsReportParametersComponent', () => {
jasmine.Ajax.uninstall();
});
it('Should initialize the Report form with a Form Group ', () => {
expect(component.reportForm.get('dateRange')).toBeDefined();
expect(component.reportForm.get('dateRange').get('startDate')).toBeDefined();
expect(component.reportForm.get('dateRange').get('endDate')).toBeDefined();
it('Should initialize the Report form with a Form Group ', (done) => {
let fakeReportParam = new ReportParametersModel(analyticParamsMock.reportDefParamTask);
component.onSuccessReportParams.subscribe(() => {
fixture.detectChanges();
expect(component.reportForm.get('taskGroup')).toBeDefined();
expect(component.reportForm.get('taskGroup').get('taskName')).toBeDefined();
done();
});
component.onSuccessReportParams.emit(fakeReportParam);
});
it('Should render a dropdown with all the status when the definition parameter type is \'status\' ', (done) => {
component.onSuccessReportParams.subscribe(() => {
fixture.detectChanges();
let dropDown: any = element.querySelector('#select-status');
expect(element.querySelector('h4').innerHTML).toEqual('Fake Task overview status');
expect(element.querySelector('h4').textContent.trim()).toEqual('Fake Task overview status');
expect(dropDown).toBeDefined();
expect(dropDown.length).toEqual(4);
expect(dropDown[0].innerHTML).toEqual('Choose One');
@@ -320,13 +327,14 @@ describe('AnalyticsReportParametersComponent', () => {
responseText: analyticParamsMock.reportDefParamProcessDef
});
jasmine.Ajax.stubRequest('http://localhost:9999/activiti-app/api/enterprise/process-definitions').andReturn({
let appId = '1';
jasmine.Ajax.stubRequest('http://localhost:9999/activiti-app/api/enterprise/process-definitions?appDefinitionId=' + appId).andReturn({
status: 200,
contentType: 'json',
responseText: analyticParamsMock.reportDefParamProcessDefOptionsApp
});
let appId = '1';
component.appId = appId;
component.reportId = '1';
let change = new SimpleChange(null, appId);
@@ -334,6 +342,24 @@ describe('AnalyticsReportParametersComponent', () => {
});
it('Should create an empty valid form when there are no parameters definitions', () => {
component.onSuccess.subscribe((res) => {
expect(component.reportForm).toBeDefined();
expect(component.reportForm.valid).toEqual(true);
expect(component.reportForm.controls).toEqual({});
});
let reportId = 1;
let change = new SimpleChange(null, reportId);
component.ngOnChanges({ 'reportId': change });
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: analyticParamsMock.reportNoParameterDefinitions
});
});
it('Should load the task list when a process definition is selected', () => {
component.onSuccessReportParams.subscribe((res) => {
expect(res).toBeDefined();
@@ -400,5 +426,148 @@ describe('AnalyticsReportParametersComponent', () => {
let numberConvert = component.convertNumber('2');
expect(numberConvert).toEqual(2);
});
describe('When the form is rendered correctly', () => {
let values: any = {
dateRange: {
startDate: '2016-09-01', endDate: '2016-10-05'
},
statusGroup: {
status: 'All'
},
processDefGroup: {
processDefinitionId: 'FakeProcess:1:22'
},
taskGroup: {
taskName: 'FakeTaskName'
},
durationGroup: {
duration: 22
},
dateIntervalGroup: {
dateRangeInterval: 120
},
processInstanceGroup: {
slowProcessInstanceInteger: 2
},
typeFilteringGroup: {
typeFiltering: true
}
};
beforeEach(async(() => {
let reportId = 1;
let change = new SimpleChange(null, reportId);
component.ngOnChanges({ 'reportId': change });
fixture.detectChanges();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: analyticParamsMock.reportDefParamStatus
});
fixture.whenStable().then(() => {
component.toggleParameters();
component.reportId = '1';
spyOn(component, 'isFormValid').and.returnValue(true);
fixture.detectChanges();
});
}));
it('Should be able to change the report title', async(() => {
let title: HTMLElement = element.querySelector('h4');
title.click();
fixture.detectChanges();
let reportName: HTMLInputElement = <HTMLInputElement> element.querySelector('#reportName');
expect(reportName).not.toBeNull();
reportName.focus();
component.reportParameters.name = 'FAKE_TEST_NAME';
reportName.value = 'FAKE_TEST_NAME';
reportName.blur();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: analyticParamsMock.reportDefParamStatus
});
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let titleChanged: HTMLElement = element.querySelector('h4');
expect(titleChanged.textContent.trim()).toEqual('FAKE_TEST_NAME');
});
}));
it('Should show a dialog to allowing report save', async(() => {
component.saveReportSuccess.subscribe(() => {
let reportDialogTitle: HTMLElement = <HTMLElement>element.querySelector('#report-dialog');
expect(reportDialogTitle.getAttribute('open')).toBeNull();
});
component.submit(values);
fixture.detectChanges();
let saveButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#save-button');
expect(saveButton).toBeDefined();
expect(saveButton).not.toBeNull();
saveButton.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let reportDialogTitle: HTMLElement = <HTMLElement>element.querySelector('#report-dialog-title');
let saveTitleSubMessage: HTMLElement = <HTMLElement> element.querySelector('#save-title-submessage');
let inputSaveName: HTMLInputElement = <HTMLInputElement> element.querySelector('#repName');
let performActionButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#action-dialog-button');
let todayDate = component.getTodayDate();
expect(reportDialogTitle).not.toBeNull();
expect(saveTitleSubMessage).not.toBeNull();
expect(inputSaveName.value.trim()).toEqual(analyticParamsMock.reportDefParamStatus.name + ' ( ' + todayDate + ' )');
expect(performActionButton).not.toBeNull();
performActionButton.click();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json'
});
});
}));
it('Should show a dialog to allowing report export', async(() => {
component.submit(values);
spyOn(component, 'generateDownloadElement').and.stub();
fixture.detectChanges();
let exportButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#export-button');
expect(exportButton).toBeDefined();
expect(exportButton).not.toBeNull();
exportButton.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let reportDialogTitle: HTMLElement = <HTMLElement>element.querySelector('#report-dialog-title');
let inputSaveName: HTMLInputElement = <HTMLInputElement> element.querySelector('#repName');
let performActionButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#action-dialog-button');
let todayDate = component.getTodayDate();
expect(reportDialogTitle).not.toBeNull();
expect(inputSaveName.value.trim()).toEqual(analyticParamsMock.reportDefParamStatus.name + ' ( ' + todayDate + ' )');
expect(performActionButton).not.toBeNull();
performActionButton.click();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json'
});
});
}));
it('Should raise an event for report deleted', async(() => {
let deleteButton: HTMLButtonElement = <HTMLButtonElement>element.querySelector('#delete-button');
expect(deleteButton).toBeDefined();
expect(deleteButton).not.toBeNull();
component.deleteReportSuccess.subscribe((reportId) => {
expect(reportId).not.toBeNull();
});
deleteButton.click();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json'
});
}));
});
});
});
@@ -24,7 +24,8 @@ import {
Output,
SimpleChanges,
OnDestroy,
AfterViewChecked
AfterViewChecked,
ViewChild
} from '@angular/core';
import { FormGroup, FormBuilder, FormControl } from '@angular/forms';
import * as moment from 'moment';
@@ -38,6 +39,7 @@ import {
} from '../models/report.model';
declare var componentHandler;
declare let dialogPolyfill: any;
@Component({
moduleId: module.id,
@@ -47,7 +49,7 @@ declare var componentHandler;
})
export class AnalyticsReportParametersComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked {
public static FORMAT_DATE_ACTIVITI: string = 'YYYY-MM-DD';
public static FORMAT_DATE_ACTIVITI: string = 'YYYY-MM-DD';
@Input()
appId: string;
@@ -55,6 +57,9 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
@Input()
reportId: string;
@Input()
hideComponent: boolean = false;
@Input()
debug: boolean = false;
@@ -70,6 +75,15 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
@Output()
onFormValueChanged = new EventEmitter();
@Output()
saveReportSuccess = new EventEmitter();
@Output()
deleteReportSuccess = new EventEmitter();
@ViewChild('reportNameDialog')
reportNameDialog: any;
onDropdownChanged = new EventEmitter();
onSuccessReportParams = new EventEmitter();
@@ -84,6 +98,9 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
private reportParamsSub;
private paramOpts;
private isEditable: boolean = false;
private action: string;
private reportParamQuery: ReportQuery;
private reportName: string;
private hideParameters: boolean = true;
constructor(private translateService: AlfrescoTranslationService,
@@ -96,8 +113,6 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
}
ngOnInit() {
this.initForm();
this.dropDownSub = this.onDropdownChanged.subscribe((field) => {
let paramDependOn: ReportParameterDetailsModel = this.reportParameters.definition.parameters.find(p => p.dependsOn === field.id);
if (paramDependOn) {
@@ -108,10 +123,9 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
this.paramOpts = this.onSuccessReportParams.subscribe((report: ReportParametersModel) => {
if (report.hasParameters()) {
this.retrieveParameterOptions(report.definition.parameters, this.appId);
this.generateFormGroupFromParameter(report.definition.parameters);
}
});
this.reportForm.valueChanges.subscribe(data => this.onValueChanged(data));
}
ngOnChanges(changes: SimpleChanges) {
@@ -127,31 +141,54 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
}
}
initForm() {
this.reportForm = this.formBuilder.group({
dateRange: new FormGroup({}),
statusGroup: new FormGroup({
status: new FormControl()
}),
processInstanceGroup: new FormGroup({
slowProcessInstanceInteger: new FormControl()
}),
taskGroup: new FormGroup({
taskName: new FormControl()
}),
typeFilteringGroup: new FormGroup({
typeFiltering: new FormControl()
}),
dateIntervalGroup: new FormGroup({
dateRangeInterval: new FormControl()
}),
durationGroup: new FormGroup({
duration: new FormControl()
}),
processDefGroup: new FormGroup({
processDefinitionId: new FormControl()
})
private generateFormGroupFromParameter(parameters: ReportParameterDetailsModel[]) {
let formBuilderGroup: any = {};
parameters.forEach((param: ReportParameterDetailsModel) => {
switch (param.type) {
case 'dateRange' :
formBuilderGroup.dateRange = new FormGroup({});
break;
case 'processDefinition':
formBuilderGroup.processDefGroup = new FormGroup({
processDefinitionId: new FormControl()
});
break;
case 'duration':
formBuilderGroup.durationGroup = new FormGroup({
duration: new FormControl()
});
break;
case 'dateInterval':
formBuilderGroup.dateIntervalGroup = new FormGroup({
dateRangeInterval: new FormControl()
});
break;
case 'boolean':
formBuilderGroup.typeFilteringGroup = new FormGroup({
typeFiltering: new FormControl()
});
break;
case 'task':
formBuilderGroup.taskGroup = new FormGroup({
taskName: new FormControl()
});
break;
case 'integer':
formBuilderGroup.processInstanceGroup = new FormGroup({
slowProcessInstanceInteger: new FormControl()
});
break;
case 'status':
formBuilderGroup.statusGroup = new FormGroup({
status: new FormControl()
});
break;
default:
return;
}
});
this.reportForm = this.formBuilder.group(formBuilderGroup);
this.reportForm.valueChanges.subscribe(data => this.onValueChanged(data));
}
public getReportParams(reportId: string) {
@@ -161,6 +198,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
if (this.reportParameters.hasParameters()) {
this.onSuccessReportParams.emit(res);
} else {
this.reportForm = this.formBuilder.group({});
this.onSuccess.emit();
}
},
@@ -193,8 +231,8 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
}
public submit(values: any) {
let reportParamQuery = this.convertFormValuesToReportParamQuery(values);
this.onSuccess.emit(reportParamQuery);
this.reportParamQuery = this.convertFormValuesToReportParamQuery(values);
this.onSuccess.emit(this.reportParamQuery);
}
onValueChanged(values: any) {
@@ -209,21 +247,41 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
.format(AnalyticsReportParametersComponent.FORMAT_DATE_ACTIVITI) + 'T00:00:00.000Z';
}
public getTodayDate() {
return moment().format(AnalyticsReportParametersComponent.FORMAT_DATE_ACTIVITI);
}
public convertNumber(value: string): number {
return value != null ? parseInt(value, 10) : 0;
}
convertFormValuesToReportParamQuery(values: any): ReportQuery {
let reportParamQuery: ReportQuery = new ReportQuery();
reportParamQuery.dateRange.startDate = this.convertMomentDate(values.dateRange.startDate);
reportParamQuery.dateRange.endDate = this.convertMomentDate(values.dateRange.endDate);
reportParamQuery.status = values.statusGroup.status;
reportParamQuery.processDefinitionId = values.processDefGroup.processDefinitionId;
reportParamQuery.taskName = values.taskGroup.taskName;
reportParamQuery.duration = values.durationGroup.duration;
reportParamQuery.dateRangeInterval = values.dateIntervalGroup.dateRangeInterval;
reportParamQuery.slowProcessInstanceInteger = this.convertNumber(values.processInstanceGroup.slowProcessInstanceInteger);
reportParamQuery.typeFiltering = values.typeFilteringGroup.typeFiltering;
if (values.dateRange) {
reportParamQuery.dateRange.startDate = this.convertMomentDate(values.dateRange.startDate);
reportParamQuery.dateRange.endDate = this.convertMomentDate(values.dateRange.endDate);
}
if (values.statusGroup) {
reportParamQuery.status = values.statusGroup.status;
}
if (values.processDefGroup) {
reportParamQuery.processDefinitionId = values.processDefGroup.processDefinitionId;
}
if (values.taskGroup) {
reportParamQuery.taskName = values.taskGroup.taskName;
}
if (values.durationGroup) {
reportParamQuery.duration = values.durationGroup.duration;
}
if (values.dateIntervalGroup) {
reportParamQuery.dateRangeInterval = values.dateIntervalGroup.dateRangeInterval;
}
if (values.processInstanceGroup) {
reportParamQuery.slowProcessInstanceInteger = this.convertNumber(values.processInstanceGroup.slowProcessInstanceInteger);
}
if (values.typeFilteringGroup) {
reportParamQuery.typeFiltering = values.typeFilteringGroup.typeFiltering;
}
return reportParamQuery;
}
@@ -236,7 +294,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
}
public editEnable() {
this.isEditable = true;
this.isEditable = true;
}
public editDisable() {
@@ -256,6 +314,74 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges, On
);
}
public showDialog(event: string) {
if (!this.reportNameDialog.nativeElement.showModal) {
dialogPolyfill.registerDialog(this.reportNameDialog.nativeElement);
}
this.reportNameDialog.nativeElement.showModal();
this.action = event;
this.reportName = this.reportParameters.name + ' ( ' + this.getTodayDate() + ' )';
}
closeDialog() {
if (this.reportNameDialog) {
this.reportNameDialog.nativeElement.close();
}
}
performAction(action: string, reportParamQuery: ReportQuery) {
reportParamQuery.reportName = this.reportName;
this.closeDialog();
if (action === 'Save') {
this.doSave(reportParamQuery);
} else if (action === 'Export') {
this.doExport(reportParamQuery);
}
this.resetActions();
}
resetActions() {
this.action = '';
this.reportName = '';
}
isFormValid() {
return this.reportForm && this.reportForm.valid && this.reportForm.dirty;
}
isSaveAction() {
return this.action === 'Save';
}
doExport(paramQuery: ReportQuery) {
this.analyticsService.exportReportToCsv(this.reportId, paramQuery).subscribe(
(data: any) => {
let blob: Blob = new Blob([data], { type: 'text/csv' });
let downloadUrl = window.URL.createObjectURL(blob);
this.generateDownloadElement(downloadUrl, paramQuery);
});
}
generateDownloadElement(downloadUrl: string, paramQuery: ReportQuery) {
let downloadElement = window.document.createElement('a');
downloadElement.setAttribute('id', 'export-download');
downloadElement.setAttribute('href', downloadUrl);
downloadElement.setAttribute('download', paramQuery.reportName);
downloadElement.click();
}
doSave(paramQuery: ReportQuery) {
this.analyticsService.saveReport(this.reportId, paramQuery).subscribe(() => {
this.saveReportSuccess.emit();
});
}
deleteReport(reportId: string) {
this.analyticsService.deleteReport(reportId).subscribe(() => {
this.deleteReportSuccess.emit(reportId);
}, error => this.logService.error(error));
}
ngAfterViewChecked() {
// workaround for MDL issues with dynamic components
if (componentHandler) {
@@ -1,7 +1,10 @@
<div class="col-md-6">
<analytics-report-parameters [appId]="appId" [reportId]="reportId"
[hideComponent]="hideParameters"
(onFormValueChanged)="reset()"
(onSuccess)="showReport($event)"
(saveReportSuccess)="onSaveReportSuccess()"
(deleteReportSuccess)="onDeleteReportSuccess()"
(onEdit)="onEditReport($event)">
</analytics-report-parameters>
@@ -35,12 +35,21 @@ export class AnalyticsComponent implements OnChanges {
@Input()
reportId: number;
@Input()
hideParameters: boolean = false;
@Input()
debug: boolean = false;
@Output()
editReport = new EventEmitter();
@Output()
reportSaved = new EventEmitter();
@Output()
reportDeleted = new EventEmitter();
@ViewChild('analyticsgenerator')
analyticsgenerator: AnalyticsGeneratorComponent;
@@ -71,4 +80,12 @@ export class AnalyticsComponent implements OnChanges {
this.editReport.emit(name);
}
public onSaveReportSuccess() {
this.reportSaved.emit();
}
public onDeleteReportSuccess() {
this.reportDeleted.emit();
}
}
@@ -16,7 +16,7 @@
*/
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, Validators } from '@angular/forms';
import { FormGroup, Validators, FormControl } from '@angular/forms';
import { WidgetComponent } from './../widget.component';
@Component({
@@ -54,7 +54,20 @@ export class DropdownWidget extends WidgetComponent {
ngOnInit() {
if (this.required) {
this.formGroup.get(this.controllerName).setValidators(Validators.required);
this.formGroup.get(this.controllerName).setValidators(Validators.compose(this.buildValidatorList()));
}
}
validateDropDown(controller: FormControl) {
return controller.value !== 'null' ? null : { controllerName: false };
}
buildValidatorList() {
let validatorList = [];
validatorList.push(Validators.required);
if (this.showDefaultOption) {
validatorList.push(this.validateDropDown);
}
return validatorList;
}
}
@@ -6,7 +6,10 @@
"FILL-PARAMETER": "Fill in the parameters to generate your report",
"NO-DATA-FOUND": "No data found",
"ZERO-DATA-FOUND": "There are only zero values",
"SETTING-TITLE": "Change report setting"
"ICON-SETTING": "Settings",
"ICON-SAVE": "Save",
"ICON-DELETE": "Delete",
"ICON-EXPORT-CSV": "Export to CSV"
}
},
"__KEY_REPORTING": {
@@ -43,5 +46,9 @@
},
"PROCESS-STATUS": "Process status",
"TASK-STATUS": "Task status"
},
"DIALOG":{
"SAVE_MESSAGE" : "The current parameter settings will be stored, and a new report will be shown in the reports list. When that particular report is clicked, the report will be generated using these saved parameter values.",
"EXPORT_MESSAGE":""
}
}
@@ -6,7 +6,10 @@
"FILL-PARAMETER": "Riempi tutti i campi per generare il report",
"NO-DATA-FOUND": "Nessun valore trovato",
"ZERO-DATA-FOUND": "Ci sono solo valori che valgono zero",
"SETTING-TITLE": "Modifica i parametri del report"
"ICON-SETTING": "Modifica i parametri del report",
"ICON-SAVE": "Salva",
"ICON-DELETE": "Cancella",
"ICON-EXPORT-CSV": "Esporta come CSV"
}
},
"__KEY_REPORTING": {
@@ -20,11 +20,13 @@ import * as moment from 'moment';
export class Chart {
id: string;
type: string;
icon: string;
constructor(obj?: any) {
this.id = obj && obj.id || null;
if (obj && obj.type) {
this.type = this.convertType(obj.type);
this.icon = this.getIconType(this.type);
}
}
@@ -58,6 +60,37 @@ export class Chart {
}
return chartType;
}
private getIconType(type: string): string {
let typeIcon: string = '';
switch (type) {
case 'pie':
typeIcon = 'pie_chart';
break;
case 'table':
typeIcon = 'web';
break;
case 'line':
typeIcon = 'show_chart';
break;
case 'bar':
typeIcon = 'equalizer';
break;
case 'multiBar':
typeIcon = 'poll';
break;
case 'HeatMap':
typeIcon = 'share';
break;
case 'masterDetailTable':
typeIcon = 'subtitles';
break;
default:
typeIcon = 'web';
break;
}
return typeIcon;
}
}
export class LineChart extends Chart {
@@ -106,6 +106,7 @@ export class ParameterValueModel {
}
export class ReportQuery {
reportName: string;
processDefinitionId: string;
status: string;
taskName: string;
@@ -116,6 +117,7 @@ export class ReportQuery {
duration: number;
constructor(obj?: any) {
this.reportName = obj && obj.reportName || null;
this.processDefinitionId = obj && obj.processDefinitionId || null;
this.status = obj && obj.status || null;
this.taskName = obj && obj.taskName || null;
@@ -125,15 +127,18 @@ export class ReportQuery {
this.duration = obj && obj.duration || 0;
this.dateRange = new ReportDateRange(obj);
}
}
export class ReportDateRange {
startDate: string;
endDate: string;
rangeId: string;
constructor(obj?: any) {
this.startDate = obj && obj.startDate || null;
this.endDate = obj && obj.endDate || null;
this.rangeId = obj && obj.rangeId || null;
}
}
@@ -126,7 +126,8 @@ export class AnalyticsService {
}
getProcessDefinitionsValues(appId: string): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().activiti.processDefinitionsApi.getProcessDefinitions(appId))
let options = { 'appDefinitionId': appId};
return Observable.fromPromise(this.apiService.getInstance().activiti.processDefinitionsApi.getProcessDefinitions(options))
.map((res: any) => {
let paramOptions: ParameterValueModel[] = [];
res.data.forEach((opt) => {
@@ -184,6 +185,28 @@ export class AnalyticsService {
}).catch(err => this.handleError(err));
}
exportReportToCsv(reportId: string, paramsQuery: any): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().activiti.reportApi.exportToCsv(reportId, paramsQuery))
.map((res: any) => {
this.logService.info('export');
return res;
}).catch(err => this.handleError(err));
}
saveReport(reportId: string, paramsQuery: any): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().activiti.reportApi.saveReport(reportId, paramsQuery))
.map(() => {
this.logService.info('save');
}).catch(err => this.handleError(err));
}
deleteReport(reportId: string): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().activiti.reportApi.deleteReport(reportId))
.map(() => {
this.logService.info('delete');
}).catch(err => this.handleError(err));
}
private handleError(error: Response) {
this.logService.error(error);
return Observable.throw(error.json().error || 'Server error');
@@ -96,8 +96,8 @@ Follow the 3 steps below:
- ng2-activiti-diagrams
- raphael
Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) .
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## Basic usage example Activiti Diagrams
@@ -12,6 +12,7 @@
"server": "wsrv -o -s -l",
"build": "npm run tslint && npm run clean-build && npm run tsc",
"build:w": "npm run tslint && rimraf dist && npm run tsc:w",
"travis": "npm link ng2-alfresco-core",
"tsc": "tsc",
"tsc:w": "tsc -w",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts -e '{,**/}**.d.ts'"
@@ -42,15 +43,15 @@
"@angular/material": "2.0.0-beta.1",
"@angular/router": "3.2.2",
"@angular/upgrade": "2.2.2",
"alfresco-js-api": "~1.1.0",
"alfresco-js-api": "~1.2.0",
"core-js": "^2.4.1",
"dialog-polyfill": "^0.4.3",
"element.scrollintoviewifneeded-polyfill": "^1.0.1",
"intl": "1.2.4",
"material-design-icons": "2.2.3",
"material-design-lite": "1.2.1",
"ng2-activiti-diagrams": "^1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-activiti-diagrams": "1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-translate": "2.5.0",
"raphael": "^2.2.6",
"reflect-metadata": "^0.1.3",
@@ -1,7 +1,7 @@
{
"name": "ng2-activiti-diagrams",
"description": "Activiti Angular2 Diagrams Component",
"version": "1.1.0",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
@@ -55,12 +55,12 @@
"zone.js": "^0.6.23",
"raphael": "^2.2.6",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-alfresco-core": "1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-alfresco-core": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"autoprefixer": "^6.5.4",
"concurrently": "^2.2.0",
"cpx": "1.3.1",
+28 -2
View File
@@ -114,8 +114,8 @@ Follow the 3 steps below:
- alfresco-js-api
- ng2-activiti-form
Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) .
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## ActivitiForm Component
@@ -351,6 +351,32 @@ There are two additional functions that can be of a great value when controlling
**Please note that if `event.preventDefault()` is not called then default outcome behaviour
will also be executed after your custom code.**
## Activiti Content Component
### Basic usage
The component shows the content preview.
```html
<activiti-content [contentId]="'1001'"></activiti-content>
```
### Configuration
#### Properties
The recommended set of properties can be found in the following table:
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `contentId` | string | | The content id to show. |
#### Events
| Name | Description |
| --- | --- |
| `contentClick` | Invoked when the content is clicked. |
## FormService
```ts
@@ -12,6 +12,7 @@
"server": "wsrv -o -s -l",
"build": "npm run tslint && npm run clean-build && npm run tsc",
"build:w": "npm run tslint && rimraf dist && npm run tsc:w",
"travis": "npm link ng2-alfresco-core",
"tsc": "tsc",
"tsc:w": "tsc -w",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts -e '{,**/}**.d.ts'"
@@ -55,9 +56,9 @@
"moment": "2.15.1",
"md-date-time-picker": "^2.2.0",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-activiti-form": "^1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-activiti-form": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
@@ -19,6 +19,7 @@ import { NgModule, ModuleWithProviders } from '@angular/core';
import { CoreModule } from 'ng2-alfresco-core';
import { ActivitiForm } from './src/components/activiti-form.component';
import { ActivitiContent } from './src/components/activiti-content.component';
import { FormFieldComponent } from './src/components/form-field/form-field.component';
import { ActivitiStartForm } from './src/components/activiti-start-form.component';
import { FormService } from './src/services/form.service';
@@ -31,6 +32,7 @@ import { HttpModule } from '@angular/http';
import { WIDGET_DIRECTIVES } from './src/components/widgets/index';
export * from './src/components/activiti-form.component';
export * from './src/components/activiti-content.component';
export * from './src/components/activiti-start-form.component';
export * from './src/services/form.service';
export * from './src/components/widgets/index';
@@ -41,6 +43,7 @@ export * from './src/events/index';
export const ACTIVITI_FORM_DIRECTIVES: any[] = [
ActivitiForm,
ActivitiContent,
ActivitiStartForm,
FormFieldComponent,
...WIDGET_DIRECTIVES
@@ -1,7 +1,7 @@
{
"name": "ng2-activiti-form",
"description": "Alfresco Activiti Form Component for Angular 2",
"version": "1.1.0",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
@@ -63,12 +63,12 @@
"moment": "2.15.1",
"md-date-time-picker": "^2.2.0",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-alfresco-core": "1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-alfresco-core": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"autoprefixer": "^6.5.4",
"concurrently": "^2.2.0",
"cpx": "^1.3.1",
@@ -0,0 +1,42 @@
.upload-widget {
width: 100%;
word-break: break-all;
}
.upload-widget__icon {
float: left;
color: rgba(0, 0, 0, .26);
}
.upload-widget__file {
float: left;
margin-top: 4px;
color: rgba(0, 0, 0, .26);
}
.upload-widget__label {
color: rgba(0, 0, 0, .26);
}
.img-upload-widget {
width: 100%;
height: 100%;
border: 1px solid rgba(117, 117, 117, 0.57);
box-shadow: 1px 1px 2px #dddddd;
background-color: #ffffff;
}
.nothing-to-see {
box-shadow: 1px 1px 2px #dddddd;
background-color: #ffffff;
width: 100%;
height: 100%;
padding: 50px 0 50px 0;
text-align: center;
}
.previewTxt {
word-wrap: break-word;
word-break: break-all;
text-align: center;
}
@@ -0,0 +1,22 @@
<div class="upload-widget" *ngIf="content">
<div class="mdl-card mdl-shadow--2dp">
<div class="mdl-card__title mdl-card--expand">
<div *ngIf="content.isThumbnailSupported()">
<img class="img-upload-widget" [src]="sanitizeUrl(content.thumbnailUrl)">
</div>
<div *ngIf="!content.isThumbnailSupported()">
<i class="material-icons">image</i>
<div class="previewTxt">{{ 'FORM.PREVIEW.IMAGE_NOT_AVAILABLE' | translate }}</div>
</div>
</div>
<div class="mdl-card__supporting-text">{{content.name}}</div>
<div class="mdl-card__actions mdl-card--border">
<button (click)="openViewer(content)" class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">zoom_in</i>
</button>
<a (click)="download($event)" [href]="content.contentRawUrl" target="_blank" [download]='content.name' class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">file_download</i>
</a>
</div>
</div>
</div>
@@ -0,0 +1,117 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Component,
OnChanges,
SimpleChanges,
Input,
Output,
EventEmitter
} from '@angular/core';
import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core';
import { FormService } from './../services/form.service';
import { ContentLinkModel } from './widgets/core/content-link.model';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
moduleId: module.id,
selector: 'activiti-content',
templateUrl: './activiti-content.component.html',
styleUrls: ['./activiti-content.component.css']
})
export class ActivitiContent implements OnChanges {
@Input()
id: string;
@Output()
contentClick = new EventEmitter();
content: ContentLinkModel;
constructor(private translate: AlfrescoTranslationService,
protected formService: FormService,
private logService: LogService,
private sanitizer: DomSanitizer ) {
if (this.translate) {
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/src');
}
}
ngOnChanges(changes: SimpleChanges) {
let contentId = changes['id'];
if (contentId && contentId.currentValue) {
this.loadContent(contentId.currentValue);
return;
}
}
loadContent(id: number) {
this.formService
.getFileContent(id)
.subscribe(
(response: ContentLinkModel) => {
this.content = new ContentLinkModel(response);
this.loadThumbnailUrl(this.content);
},
error => {
this.logService.error(error);
}
);
}
loadThumbnailUrl(content: ContentLinkModel) {
if (this.content.isTypeImage()) {
this.formService.getFileRawContent(content.id).subscribe(
(response: Blob) => {
this.content.thumbnailUrl = this.createUrlPreview(response);
},
error => {
this.logService.error(error);
}
);
} else if (this.content.isThumbnailSupported()) {
this.content.contentRawUrl = this.formService.getFileRawContentUrl(content.id);
this.content.thumbnailUrl = this.formService.getContentThumbnailUrl(content.id);
}
}
openViewer(content: ContentLinkModel) {
this.contentClick.emit(content);
this.logService.info('Content clicked' + content.id);
}
/**
* Download file opening it in a new window
*/
download($event) {
$event.stopPropagation();
}
private sanitizeUrl(url: string) {
return this.sanitizer.bypassSecurityTrustUrl(url);
}
private createUrlPreview(blob: Blob) {
let imageUrl = window.URL.createObjectURL(blob);
let sanitize: any = this.sanitizeUrl(imageUrl);
return sanitize.changingThisBreaksApplicationSecurity;
}
}
@@ -425,7 +425,7 @@ describe('ActivitiForm', () => {
expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
});
it('should fetch and parse form by task id', () => {
it('should fetch and parse form by task id', (done) => {
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
@@ -435,19 +435,18 @@ describe('ActivitiForm', () => {
});
const taskId = '456';
let loaded = false;
formComponent.formLoaded.subscribe(() => loaded = true);
formComponent.formLoaded.subscribe(() => {
expect(formService.getTaskForm).toHaveBeenCalledWith(taskId);
expect(formComponent.form).toBeDefined();
expect(formComponent.form.taskId).toBe(taskId);
done();
});
expect(formComponent.form).toBeUndefined();
formComponent.getFormByTaskId(taskId);
expect(loaded).toBeTruthy();
expect(formService.getTaskForm).toHaveBeenCalledWith(taskId);
expect(formComponent.form).toBeDefined();
expect(formComponent.form.taskId).toBe(taskId);
});
it('should handle error when getting form by task id', () => {
it('should handle error when getting form by task id', (done) => {
const error = 'Some error';
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
@@ -456,11 +455,13 @@ describe('ActivitiForm', () => {
return Observable.throw(error);
});
formComponent.getFormByTaskId('123');
expect(formComponent.handleError).toHaveBeenCalledWith(error);
formComponent.getFormByTaskId('123').then(_ => {
expect(formComponent.handleError).toHaveBeenCalledWith(error);
done();
});
});
it('should apply readonly state when getting form by task id', () => {
it('should apply readonly state when getting form by task id', (done) => {
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
@@ -470,10 +471,11 @@ describe('ActivitiForm', () => {
});
formComponent.readOnly = true;
formComponent.getFormByTaskId('123');
expect(formComponent.form).toBeDefined();
expect(formComponent.form.readOnly).toBe(true);
formComponent.getFormByTaskId('123').then(_ => {
expect(formComponent.form).toBeDefined();
expect(formComponent.form.readOnly).toBe(true);
done();
});
});
it('should fetch and parse form definition by id', () => {
@@ -305,16 +305,24 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
}
}
loadFormPorcessVariable(taskId) {
this.formService.getTask(taskId).subscribe(
task => {
if (this.isAProcessTask(task)) {
this.visibilityService.getTaskProcessVariable(taskId).subscribe();
loadFormProcessVariables(taskId: string): Promise<boolean> {
return new Promise((resolve, reject) => {
this.formService.getTask(taskId).subscribe(
task => {
if (this.isAProcessTask(task)) {
this.visibilityService.getTaskProcessVariable(taskId).subscribe(_ => {
resolve(true);
});
} else {
resolve(true);
}
},
error => {
this.handleError(error);
resolve(false);
}
},
(error) => {
this.handleError(error);
});
);
});
}
isAProcessTask(taskRepresentation) {
@@ -330,20 +338,25 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
return false;
}
getFormByTaskId(taskId: string) {
this.loadFormPorcessVariable(this.taskId);
let data = this.data;
this.formService
.getTaskForm(taskId)
.subscribe(
form => {
this.form = new FormModel(form, data, this.readOnly, this.formService);
this.onFormLoaded(this.form);
},
(error) => {
this.handleError(error);
}
);
getFormByTaskId(taskId: string): Promise<FormModel> {
return new Promise<FormModel>((resolve, reject) => {
this.loadFormProcessVariables(this.taskId).then(_ => {
this.formService
.getTaskForm(taskId)
.subscribe(
form => {
this.form = new FormModel(form, this.data, this.readOnly, this.formService);
this.onFormLoaded(this.form);
resolve(this.form);
},
error => {
this.handleError(error);
// reject(error);
resolve(null);
}
);
});
});
}
getFormDefinitionByFormId(formId: string) {
@@ -21,6 +21,7 @@ import { Observable } from 'rxjs/Rx';
import { ActivitiStartForm } from './activiti-start-form.component';
import { FormFieldComponent } from './form-field/form-field.component';
import { ActivitiContent } from './activiti-content.component';
import { WIDGET_DIRECTIVES } from './widgets/index';
import { FormService } from './../services/form.service';
import { EcmModelService } from './../services/ecm-model.service';
@@ -45,6 +46,7 @@ describe('ActivitiStartForm', () => {
declarations: [
ActivitiStartForm,
FormFieldComponent,
ActivitiContent,
...WIDGET_DIRECTIVES
],
providers: [
@@ -24,6 +24,7 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { WIDGET_DIRECTIVES } from '../index';
import { FormFieldComponent } from './../../form-field/form-field.component';
import { ActivitiContent } from './../../activiti-content.component';
import { fakeFormJson } from '../../../services/assets/widget-visibility.service.mock';
describe('ContainerWidget', () => {
@@ -132,7 +133,7 @@ describe('ContainerWidget', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [FormFieldComponent, WIDGET_DIRECTIVES]
declarations: [FormFieldComponent, ActivitiContent, WIDGET_DIRECTIVES]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(ContainerWidget);
containerWidgetComponent = fixture.componentInstance;
@@ -0,0 +1,71 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class ContentLinkModel {
contentAvailable: boolean;
created: string;
createdBy: any;
id: number;
link: boolean;
mimeType: string;
name: string;
previewStatus: string;
relatedContent: boolean;
simpleType: string;
thumbnailUrl: string;
contentRawUrl: string;
thumbnailStatus: string;
constructor(obj?: any) {
this.contentAvailable = obj && obj.contentAvailable;
this.created = obj && obj.created;
this.createdBy = obj && obj.createdBy || {};
this.id = obj && obj.id;
this.link = obj && obj.link;
this.mimeType = obj && obj.mimeType;
this.name = obj && obj.name;
this.previewStatus = obj && obj.previewStatus;
this.relatedContent = obj && obj.relatedContent;
this.simpleType = obj && obj.simpleType;
this.thumbnailStatus = obj && obj.thumbnailStatus;
}
hasPreviewStatus(): boolean {
return this.previewStatus === 'supported' ? true : false;
}
isTypeImage(): boolean {
return this.simpleType === 'image' ? true : false;
}
isTypePdf(): boolean {
return this.simpleType === 'pdf' ? true : false;
}
isTypeDoc(): boolean {
return this.simpleType === 'word' || this.simpleType === 'content' ? true : false;
}
isThumbnailReady(): boolean {
return this.thumbnailStatus === 'created';
}
isThumbnailSupported(): boolean {
return this.isTypeImage() || ((this.isTypePdf() || this.isTypeDoc()) && this.isThumbnailReady());
}
}
@@ -29,3 +29,4 @@ export * from './tab.model';
export * from './form-outcome.model';
export * from './form-outcome-event.model';
export * from './form-field-validator';
export * from './content-link.model';
@@ -21,32 +21,3 @@
width: 100%;
}
.upload-widget {
width: 100%;
word-break: break-all;
}
.upload-widget__icon {
float: left;
color: rgba(0, 0, 0, .26);
}
.upload-widget__file {
float: left;
margin-top: 4px;
color: rgba(0, 0, 0, .26);
}
.upload-widget__label {
color: rgba(0, 0, 0, .26);
}
.img-upload-widget {
width: 100px;
height: 100px;
padding: 2px;
border: 1px solid rgba(117, 117, 117, 0.57);
box-shadow: 1px 1px 2px #dddddd;
background-color: #ffffff;
}
@@ -3,7 +3,7 @@
<label class="mdl-checkbox mdl-js-checkbox" [attr.for]="field.id" >
<input type="checkbox"
[attr.id]="field.id"
[checked]="field.value"
[checked]="value"
[(ngModel)]="value"
class="mdl-checkbox__input"
disabled>
@@ -68,16 +68,9 @@
</div>
</div>
<div *ngSwitchCase="'upload'">
<div class="upload-widget">
<div>
<label class="upload-widget__label" [attr.for]="field.id">{{field.name}}</label>
</div>
<div>
<img *ngIf="hasFile" class="img-upload-widget" src="{{settingsService.bpmHost}}/activiti-app/app/rest/content/{{id}}/raw">
</div>
<div>
<i *ngIf="hasFile" class="material-icons upload-widget__icon">attachment</i>
<span *ngIf="hasFile" class="upload-widget__file">{{value}}</span>
<div *ngIf="hasFile" class="mdl-grid">
<div *ngFor="let file of field.value" class="mdl-cell mdl-cell--6-col">
<activiti-content [id]="file.id"></activiti-content>
</div>
</div>
</div>
@@ -20,6 +20,7 @@ import { CoreModule, LogServiceMock } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { DisplayValueWidget } from './display-value.widget';
import { FormService } from '../../../services/form.service';
import { ActivitiContent } from '../../activiti-content.component';
import { EcmModelService } from '../../../services/ecm-model.service';
import { FormFieldModel } from './../core/form-field.model';
import { FormFieldTypes } from '../core/form-field-types';
@@ -688,7 +689,7 @@ describe('DisplayValueWidget', () => {
window['componentHandler'] = componentHandler;
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [DisplayValueWidget],
declarations: [DisplayValueWidget, ActivitiContent],
providers: [
EcmModelService,
FormService,
@@ -23,6 +23,7 @@ import { TabsWidget } from './tabs.widget';
import { TabModel } from '../core/tab.model';
import { WIDGET_DIRECTIVES } from '../index';
import { FormFieldComponent } from './../../form-field/form-field.component';
import { ActivitiContent } from './../../activiti-content.component';
import { CoreModule } from 'ng2-alfresco-core';
describe('TabsWidget', () => {
@@ -103,7 +104,7 @@ describe('TabsWidget', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [FormFieldComponent, WIDGET_DIRECTIVES]
declarations: [FormFieldComponent, ActivitiContent, WIDGET_DIRECTIVES]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TabsWidget);
tabWidgetComponent = fixture.componentInstance;
@@ -2,6 +2,9 @@
"FORM": {
"START_FORM": {
"TITLE": "Start Form"
},
"PREVIEW": {
"IMAGE_NOT_AVAILABLE": "The preview image is not available."
}
}
}
@@ -0,0 +1,10 @@
{
"FORM": {
"START_FORM": {
"TITLE": "Inizia"
},
"PREVIEW": {
"IMAGE_NOT_AVAILABLE": "Anteprima immagine non disponibile."
}
}
}
@@ -30,6 +30,7 @@ describe('FormService', () => {
let service: FormService;
let apiService: AlfrescoApiService;
let logService: LogService;
let bpmCli: any;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -44,6 +45,7 @@ describe('FormService', () => {
});
service = TestBed.get(FormService);
apiService = TestBed.get(AlfrescoApiService);
bpmCli = apiService.getInstance().bpmAuth;
logService = TestBed.get(LogService);
});
@@ -374,6 +376,81 @@ describe('FormService', () => {
});
});
it('should return the unsupported content when the file is an image', (done) => {
let contentId: number = 999;
responseBody = {
id: contentId,
name: 'fake-name.jpg',
created: '2017-01-23T12:12:53.219+0000',
createdBy: {id: 2, firstName: 'fake-admin', lastName: 'fake-last', 'email': 'fake-admin'},
relatedContent: false,
contentAvailable: true,
link: false,
mimeType: 'image/jpeg',
simpleType: 'image',
previewStatus: 'unsupported',
thumbnailStatus: 'unsupported'
};
service.getFileContent(contentId).subscribe(result => {
expect(result.id).toEqual(contentId);
expect(result.name).toEqual('fake-name.jpg');
expect(result.simpleType).toEqual('image');
expect(result.thumbnailStatus).toEqual('unsupported');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
'status': 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('should return the supported content when the file is a pdf', (done) => {
let contentId: number = 888;
responseBody = {
id: contentId,
name: 'fake-name.pdf',
created: '2017-01-23T12:12:53.219+0000',
createdBy: {id: 2, firstName: 'fake-admin', lastName: 'fake-last', 'email': 'fake-admin'},
relatedContent: false,
contentAvailable: true,
link: false,
mimeType: 'application/pdf',
simpleType: 'pdf',
previewStatus: 'created',
thumbnailStatus: 'created'
};
service.getFileContent(contentId).subscribe(result => {
expect(result.id).toEqual(contentId);
expect(result.name).toEqual('fake-name.pdf');
expect(result.simpleType).toEqual('pdf');
expect(result.thumbnailStatus).toEqual('created');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
'status': 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('should return the raw content URL', () => {
let contentId: number = 999;
let contentRawUrl = service.getFileRawContentUrl(contentId);
expect(contentRawUrl).toEqual(`${bpmCli.basePath}/api/enterprise/content/${contentId}/raw`);
});
it('should return the thumbnail URL', () => {
let contentId: number = 999;
let contentRawUrl = service.getContentThumbnailUrl(contentId);
expect(contentRawUrl).toEqual(`${bpmCli.basePath}/app/rest/content/${contentId}/rendition/thumbnail`);
});
it('should create a Form form a Node', (done) => {
let nameForm = 'testNode';
@@ -252,6 +252,26 @@ export class FormService {
return Observable.fromPromise(this.apiService.getInstance().activiti.contentApi.createTemporaryRawRelatedContent(file));
}
getFileContent(contentId: number): Observable<any> {
let alfrescoApi = this.apiService.getInstance();
return Observable.fromPromise(alfrescoApi.activiti.contentApi.getContent(contentId));
}
getFileRawContent(contentId: number): Observable<any> {
let alfrescoApi = this.apiService.getInstance();
return Observable.fromPromise(alfrescoApi.activiti.contentApi.getRawContent(contentId));
}
getFileRawContentUrl(contentId: number): string {
let alfrescoApi = this.apiService.getInstance();
return alfrescoApi.activiti.contentApi.getRawContentUrl(contentId);
}
getContentThumbnailUrl(contentId: number): string {
let alfrescoApi = this.apiService.getInstance();
return alfrescoApi.activiti.contentApi.getContentThumbnailUrl(contentId);
}
getRestFieldValues(taskId: string, field: string): Observable<any> {
let alfrescoApi = this.apiService.getInstance();
return Observable.fromPromise(alfrescoApi.activiti.taskApi.getRestFieldValues(taskId, field));
@@ -143,7 +143,7 @@ export class WidgetVisibilityService {
if (field.value && field.value.name) {
value = field.value.name;
} else if (field.options) {
let option = field.options.find(option => option.id === field.value);
let option = field.options.find(opt => opt.id === field.value);
if (option) {
value = option.name;
} else {
@@ -104,8 +104,8 @@ Follow the 3 steps below:
- ng2-activiti-tasklist
- ng2-activiti-processlist
Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) .
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## Basic usage
@@ -1,6 +1,6 @@
{
"name": "ng2-activiti-processlist-demo",
"description": "Show available processes from the Activiti BPM suite - Demo",
"description": "Show available processes from the Activiti Process Services suite - Demo",
"version": "0.1.0",
"author": "Will Abson",
"main": "index.js",
@@ -12,6 +12,7 @@
"server": "wsrv -o -s -l",
"build": "npm run tslint && npm run clean-build && npm run tsc",
"build:w": "npm run tslint && rimraf dist && npm run tsc:w",
"travis": "npm link ng2-alfresco-core ng2-alfresco-datatable ng2-activiti-form ng2-activiti-tasklist",
"tsc": "tsc",
"tsc:w": "tsc -w",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts -e '{,**/}**.d.ts'"
@@ -48,11 +49,11 @@
"moment": "2.15.1",
"md-date-time-picker": "^2.2.0",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-activiti-tasklist": "1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-alfresco-datatable": "1.1.0",
"ng2-activiti-processlist": "^1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-activiti-tasklist": "1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-alfresco-datatable": "1.2.0",
"ng2-activiti-processlist": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
@@ -1,7 +1,7 @@
{
"name": "ng2-activiti-processlist",
"description": "Show active processes from the Activiti BPM suite",
"version": "1.1.0",
"description": "Show active processes from the Activiti Process Services suite",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
@@ -62,15 +62,15 @@
"moment": "2.15.1",
"md-date-time-picker": "^2.2.0",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-activiti-form": "1.1.0",
"ng2-activiti-tasklist": "1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-alfresco-datatable": "1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-activiti-form": "1.2.0",
"ng2-activiti-tasklist": "1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-alfresco-datatable": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"autoprefixer": "^6.5.4",
"concurrently": "^2.2.0",
"cpx": "^1.3.1",
@@ -103,8 +103,8 @@ Follow the 3 steps below:
- ng2-alfresco-datatable
- ng2-activiti-tasklist
Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) .
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## Basic usage example Activiti Task List
@@ -12,6 +12,7 @@
"server": "wsrv -o -s -l",
"build": "npm run tslint && npm run clean-build && npm run tsc",
"build:w": "npm run tslint && rimraf dist && npm run tsc:w",
"travis": "npm link ng2-alfresco-core ng2-alfresco-datatable ng2-activiti-form",
"tsc": "tsc",
"tsc:w": "tsc -w",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts -e '{,**/}**.d.ts'"
@@ -42,10 +43,10 @@
"moment": "2.15.1",
"md-date-time-picker": "^2.2.0",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-alfresco-datatable": "1.1.0",
"ng2-activiti-tasklist": "^1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-alfresco-datatable": "1.2.0",
"ng2-activiti-tasklist": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
@@ -1,7 +1,7 @@
{
"name": "ng2-activiti-tasklist",
"description": "Activiti Angular2 Task List Component",
"version": "1.1.0",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
@@ -67,14 +67,14 @@
"moment": "2.15.1",
"md-date-time-picker": "^2.2.0",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-activiti-form": "1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-alfresco-datatable": "1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-activiti-form": "1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-alfresco-datatable": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"autoprefixer": "^6.5.4",
"concurrently": "^2.2.0",
"cpx": "1.3.1",
@@ -51,7 +51,7 @@ describe('ActivitiChecklist', () => {
}).compileComponents().then(() => {
let translateService = TestBed.get(AlfrescoTranslationService);
spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => {
spyOn(translateService.translate, 'get').and.callFake((key) => {
return Observable.of(key);
});
@@ -58,7 +58,7 @@ describe('ActivitiPeopleSearch', () => {
let translateService = TestBed.get(AlfrescoTranslationService);
spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => { return Observable.of(key); });
spyOn(translateService.translate, 'get').and.callFake((key) => { return Observable.of(key); });
fixture = TestBed.createComponent(ActivitiPeopleSearch);
activitiPeopleSearchComponent = fixture.componentInstance;
@@ -64,7 +64,7 @@ describe('ActivitiPeople', () => {
let translateService = TestBed.get(AlfrescoTranslationService);
spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => { return Observable.of(key); });
spyOn(translateService.translate, 'get').and.callFake((key) => { return Observable.of(key); });
fixture = TestBed.createComponent(ActivitiPeople);
activitiPeopleComponent = fixture.componentInstance;
@@ -45,7 +45,7 @@ describe('ActivitiStartTaskButton', () => {
}).compileComponents().then(() => {
let translateService = TestBed.get(AlfrescoTranslationService);
spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => { return Observable.of(key); });
spyOn(translateService.translate, 'get').and.callFake((key) => { return Observable.of(key); });
fixture = TestBed.createComponent(ActivitiStartTaskButton);
activitiStartTaskButton = fixture.componentInstance;
@@ -59,7 +59,7 @@ describe('ActivitiTaskDetails', () => {
let translateService = TestBed.get(AlfrescoTranslationService);
spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => { return Observable.of(key); });
spyOn(translateService.translate, 'get').and.callFake((key) => { return Observable.of(key); });
}));
beforeEach(() => {
@@ -47,7 +47,7 @@ describe('ActivitiTaskHeader', () => {
let translateService = TestBed.get(AlfrescoTranslationService);
spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => { return Observable.of(key); });
spyOn(translateService.translate, 'get').and.callFake((key) => { return Observable.of(key); });
}));
beforeEach(() => {
+63 -22
View File
@@ -64,7 +64,7 @@ npm install --save ng2-alfresco-core
- **ContextMenuService**, global context menu APIs
#### Alfresco Api Service
## Alfresco Api Service
Provides access to initialized **AlfrescoJSApi** instance.
@@ -96,7 +96,7 @@ let apiService: any = this.authService.getAlfrescoApi();
apiService.nodes.addNode('-root-', body, {});
```
#### Notification Service
## Notification Service
The Notification Service is implemented on top of the Angular 2 Material Design snackbar.
Use this service to show a notification message, and optionaly get feedback from it.
@@ -133,7 +133,7 @@ export class MyComponent implements OnInit {
}
```
#### Context Menu directive
## Context Menu directive
_See **Demo Shell** or **DocumentList** implementation for more details and use cases._
@@ -169,7 +169,7 @@ export class MyComponent implements OnInit {
}
```
#### Authentication Service
## Authentication Service
The authentication service is used inside the [login component](../ng2-alfresco-login) and is possible to find there an example of how to use it.
@@ -182,27 +182,24 @@ import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } fr
@Component({
selector: 'alfresco-app-demo',
template: `
<div *ngIf="!authenticated" >
Authentication failed to ip {{ ecmHost }} with user: admin, admin
</div>
<div *ngIf="authenticated">
<H5>ECM</H5>
Authentication successfull to ip {{ ecmHost }} with user: admin, admin<br>
your token is {{ tokenEcm }}<br>
<H5>BPM</H5>
Authentication successfull to ip {{ bpmHost }} with user: admin, admin<br>
your token is {{ tokenBpm }}<br>
</div>`
<div *ngIf="!authenticated" >
Authentication failed to ip {{ ecmHost }} with user: admin, admin
</div>
<div *ngIf="authenticated">
<H5>ECM</H5>
Authentication successfull to ip {{ ecmHost }} with user: admin, admin<br>
your token is {{ tokenEcm }}<br>
<H5>BPM</H5>
Authentication successfull to ip {{ bpmHost }} with user: admin, admin<br>
your token is {{ tokenBpm }}<br>
</div>
`
})
class MyDemoApp {
authenticated: boolean = false;
public ecmHost: string = 'http://localhost:8080';
public bpmHost: string = 'http://localhost:9999';
ecmHost: string = 'http://localhost:8080';
bpmHost: string = 'http://localhost:9999';
tokenBpm: string;
tokenEcm: string;
constructor(public alfrescoAuthenticationService: AlfrescoAuthenticationService,
@@ -246,7 +243,51 @@ export class AppModule {
platformBrowserDynamic().bootstrapModule(AppModule);
```
#### Renditions Service
## AlfrescoTranslationService
In order to enable localisation support you will need creating a `/resources/i18n/en.json` file
and registering path to it's parent `i18n` folder:
```ts
class MainApplication {
constructor(private translateService: AlfrescoTranslationService) {
translateService.addTranslationFolder('app', 'resources');
}
}
```
Service also allows changing current language for entire application.
Imagine you got a language picker that invokes `onLanguageClicked` method:
```ts
class MyComponent {
constructor(private translateService: AlfrescoTranslationService) {
}
onLanguageClicked(lang: string) {
this.translateService.use('en');
}
}
```
It is also possible providing custom translations for existing components by overriding their resource paths:
```ts
class MyComponent {
constructor(private translateService: AlfrescoTranslationService) {
translateService.addTranslationFolder(
'ng2-alfresco-login',
'i18n/custom-translation/alfresco-login'
);
}
}
```
**Important note**: `addTranslationFolder` method redirects **all** languages to a new folder, you may need implementing multiple languages
or copying existing translation files to a new path.
## Renditions Service
* getRenditionsListByNodeId(nodeId: string)
* createRendition(nodeId: string, encoding: string)
@@ -1,7 +1,7 @@
{
"name": "ng2-alfresco-core",
"description": "Alfresco Angular 2 Components core",
"version": "1.1.0",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
@@ -65,7 +65,7 @@
"@angular/material": "2.0.0-beta.1",
"@angular/router": "3.2.2",
"@angular/upgrade": "2.2.2",
"alfresco-js-api": "~1.1.0",
"alfresco-js-api": "~1.2.0",
"core-js": "^2.4.1",
"dialog-polyfill": "^0.4.3",
"element.scrollintoviewifneeded-polyfill": "^1.0.1",
@@ -79,8 +79,8 @@
"zone.js": "^0.6.23"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"autoprefixer": "^6.5.4",
"concurrently": "^2.2.0",
"cpx": "1.3.1",
@@ -44,6 +44,9 @@ export class AlfrescoTranslateLoader implements TranslateLoader {
getComponentToFetch(lang: string) {
let observableBatch = [];
if (!this.queue[lang]) {
this.queue[lang] = [];
}
this._componentList.forEach((component) => {
if (!this.isComponentInQueue(lang, component.name)) {
this.queue[lang].push(component.name);
@@ -67,7 +70,7 @@ export class AlfrescoTranslateLoader implements TranslateLoader {
}
isComponentInQueue(lang: string, name: string) {
return this.queue[lang].find(x => x === name) ? true : false;
return (this.queue[lang] || []).find(x => x === name) ? true : false;
}
getFullTranslationJSON(lang: string) {
@@ -22,24 +22,35 @@ import { AlfrescoTranslateLoader } from './alfresco-translate-loader.service';
@Injectable()
export class AlfrescoTranslationService {
defaultLang: string = 'en';
userLang: string = 'en';
customLoader: AlfrescoTranslateLoader;
constructor(public translate: TranslateService) {
this.userLang = translate.getBrowserLang() || 'en';
translate.setDefaultLang(this.userLang);
this.userLang = translate.getBrowserLang() || this.defaultLang;
translate.setDefaultLang(this.defaultLang);
this.customLoader = <AlfrescoTranslateLoader> this.translate.currentLoader;
this.customLoader.init(this.userLang);
this.use(this.userLang);
}
addTranslationFolder(name: string = '', path: string = '') {
if (!this.customLoader.existComponent(name)) {
this.customLoader.addComponentList(name, path);
this.translate.getTranslation(this.userLang).subscribe(
() => {
this.translate.use(this.userLang);
}
);
if (this.userLang !== this.defaultLang) {
this.translate.getTranslation(this.defaultLang).subscribe(() => {
this.translate.getTranslation(this.userLang).subscribe(
() => {
this.translate.use(this.userLang);
}
);
});
} else {
this.translate.getTranslation(this.userLang).subscribe(
() => {
this.translate.use(this.userLang);
}
);
}
}
}
@@ -93,8 +93,8 @@ Follow the 3 steps below:
- ng2-alfresco-core
- ng2-alfresco-datatable
Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) .
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## Basic usage example
@@ -177,6 +177,7 @@ platformBrowserDynamic().bootstrapModule(AppModule);
| `data` | DataTableAdapter | instance of **ObjectDataTableAdapter** | data source |
| `multiselect` | boolean | false | Toggles multiple row selection, renders checkboxes at the beginning of each row |
| `actions` | boolean | false | Toggles data actions column |
| `actionsPosition` | string (left\|right) | right | Position of the actions dropdown menu. |
| `fallbackThumbnail` | string | | Fallback image for row ehre thubnail is missing|
### Events
@@ -3,12 +3,6 @@
"description": "Alfresco Angular2 DataTable Component - Demo",
"version": "0.1.0",
"author": "Alfresco Software, Ltd.",
"contributors": [
{
"name": "Denys Vuika",
"email": "denis.vuyka@gmail.com"
}
],
"main": "index.js",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings dist",
@@ -18,11 +12,18 @@
"server": "wsrv -o -s -l",
"build": "npm run tslint && npm run clean-build && npm run tsc",
"build:w": "npm run tslint && rimraf dist && npm run tsc:w",
"travis": "npm link ng2-alfresco-core",
"tsc": "tsc",
"tsc:w": "tsc -w",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts -e '{,**/}**.d.ts'"
},
"license": "Apache-2.0",
"contributors": [
{
"name": "Denys Vuika",
"email": "denis.vuyka@gmail.com"
}
],
"dependencies": {
"@angular/common": "2.2.2",
"@angular/compiler": "2.2.2",
@@ -46,9 +47,9 @@
"material-design-icons": "2.2.3",
"material-design-lite": "1.2.1",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-alfresco-core": "1.1.0",
"ng2-alfresco-datatable": "1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-alfresco-core": "1.2.0",
"ng2-alfresco-datatable": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
@@ -1,7 +1,7 @@
{
"name": "ng2-alfresco-datatable",
"description": "Alfresco Angular2 DataTable Component",
"version": "1.1.0",
"version": "1.2.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "npm install rimraf && npm run clean-build && rimraf dist node_modules typings",
@@ -61,12 +61,12 @@
"systemjs": "0.19.27",
"zone.js": "^0.6.23",
"ng2-translate": "2.5.0",
"alfresco-js-api": "~1.1.0",
"ng2-alfresco-core": "1.1.0"
"alfresco-js-api": "~1.2.0",
"ng2-alfresco-core": "1.2.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"autoprefixer": "^6.5.4",
"concurrently": "^2.2.0",
"cpx": "1.3.1",
@@ -3,6 +3,10 @@
class="mdl-data-table mdl-js-data-table full-width mdl-data-table-fix-firefox">
<thead>
<tr>
<!-- Actions (left) -->
<th *ngIf="actions && actionsPosition === 'left'" class="alfresco-datatable__actions-header">
<span class="sr-only">Actions</span>
</th>
<!-- Columns -->
<th *ngIf="multiselect">
<label
@@ -23,8 +27,8 @@
<span *ngIf="col.srTitle" class="sr-only">{{col.srTitle}}</span>
<span *ngIf="col.title">{{col.title}}</span>
</th>
<!-- Actions -->
<th *ngIf="actions">
<!-- Actions (right) -->
<th *ngIf="actions && actionsPosition === 'right'" class="alfresco-datatable__actions-header">
<span class="sr-only">Actions</span>
</th>
</tr>
@@ -34,6 +38,23 @@
<tr *ngFor="let row of data.getRows(); let idx = index" tabindex="0"
class="alfresco-datatable__row"
[class.alfresco-datatable__row--selected]="selectedRow === row">
<!-- Actions (right) -->
<td *ngIf="actions && actionsPosition === 'left'" class="alfresco-datatable__actions-cell">
<button [id]="'action_menu_' + idx" alfresco-mdl-button class="mdl-button--icon" [attr.data-automation-id]="actions_menu">
<i class="material-icons">more_vert</i>
</button>
<ul alfresco-mdl-menu class="mdl-menu--bottom-left"
[attr.for]="'action_menu_' + idx">
<li class="mdl-menu__item"
[attr.data-automation-id]="action.title"
*ngFor="let action of getRowActions(row)"
(click)="onExecuteRowAction(row, action)">
{{action.title}}
</li>
</ul>
</td>
<td *ngIf="multiselect">
<label
class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect mdl-data-table__select"
@@ -71,8 +92,8 @@
</div>
</td>
<td *ngIf="actions">
<!-- action menu -->
<!-- Actions (right) -->
<td *ngIf="actions && actionsPosition === 'right'" class="alfresco-datatable__actions-cell">
<button [id]="'action_menu_' + idx" alfresco-mdl-button class="mdl-button--icon" [attr.data-automation-id]="actions_menu">
<i class="material-icons">more_vert</i>
</button>
@@ -15,6 +15,8 @@
* limitations under the License.
*/
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { DataTableComponent } from './datatable.component';
import {
DataRow,
@@ -26,23 +28,75 @@ import {
describe('DataTable', () => {
let fixture: ComponentFixture<DataTableComponent>;
let dataTable: DataTableComponent;
let element: any;
let eventMock: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [DataTableComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DataTableComponent);
dataTable = fixture.componentInstance;
element = fixture.debugElement.nativeElement;
// usernameInput = element.querySelector('#username');
// passwordInput = element.querySelector('#password');
fixture.detectChanges();
});
beforeEach(() => {
// reset MDL handler
window['componentHandler'] = null;
dataTable = new DataTableComponent();
// dataTable = new DataTableComponent();
eventMock = {
preventDefault: function () {}
};
});
it('should put actions menu to the right by default', () => {
dataTable.data = new ObjectDataTableAdapter([], [
<DataColumn> {},
<DataColumn> {},
<DataColumn> {}
]);
dataTable.actions = true;
fixture.detectChanges();
let headers = element.querySelectorAll('th');
expect(headers.length).toBe(4);
expect(headers[headers.length - 1].classList.contains('alfresco-datatable__actions-header')).toBeTruthy();
});
it('should put actions menu to the left', () => {
dataTable.data = new ObjectDataTableAdapter([], [
<DataColumn> {},
<DataColumn> {},
<DataColumn> {}
]);
dataTable.actions = true;
dataTable.actionsPosition = 'left';
fixture.detectChanges();
let headers = element.querySelectorAll('th');
expect(headers.length).toBe(4);
expect(headers[0].classList.contains('alfresco-datatable__actions-header')).toBeTruthy();
});
it('should initialize default adapter', () => {
expect(dataTable.data).toBeUndefined();
dataTable.ngOnInit();
expect(dataTable.data).toEqual(jasmine.any(ObjectDataTableAdapter));
let table = new DataTableComponent();
expect(table.data).toBeUndefined();
table.ngOnInit();
expect(table.data).toEqual(jasmine.any(ObjectDataTableAdapter));
});
it('should initialize with custom data', () => {
@@ -44,6 +44,9 @@ export class DataTableComponent implements OnInit {
@Input()
actions: boolean = false;
@Input()
actionsPosition: string = 'right'; // left|right
@Input()
fallbackThumbnail: string;
@@ -92,8 +92,8 @@ Follow the 3 steps below:
- ng2-alfresco-datatable
- ng2-alfresco-documentlist
Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) .
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
## Basic usage
@@ -181,6 +181,7 @@ The properties currentFolderId, folderNode and node are the entry initialization
| `fallbackThumbnail` | string | | Path to fallback image to use if the row thumbnail is missing |
| `multiselect` | boolean | false | Toggles multiselect mode |
| `contentActions` | boolean | false | Toggles content actions for each row |
| `contentActionsPosition` | string (left\|right) | right | Position of the content actions dropdown menu. |
| `contextMenuActions` | boolean | false | Toggles context menus for each row |
| `enablePagination` | boolean | true | Shows pagination |
| `creationMenuActions` | boolean | true | Toggles the creation menu actions|
@@ -488,7 +489,7 @@ context.row.getValue('name')
context.row.getValue('createdByUser.displayName')
```
_You may want using **row** api to get raw value access.
You may want using **row** api to get raw value access.
```html
<content-column title="Name" key="name" sortable="true" class="full-width ellipsis-cell">
@@ -501,7 +502,7 @@ _You may want using **row** api to get raw value access.
Use **data** api to get values with post-processing, like datetime/icon conversion._
Final example, we'll name the context as `entry`:
In the Example below will prepend `Hi!` to each file and folder name in the list:
```html
<content-column title="Name" key="name" sortable="true" class="full-width ellipsis-cell">
@@ -511,9 +512,21 @@ Final example, we'll name the context as `entry`:
</content-column>
```
Example above will prepend `Hi!` to each file and folder name in the list.
In the Example below will add the [ng2-alfresco-tag](https://www.npmjs.com/package/ng2-alfresco-tag) component is integrate in the document list.
```html
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.TAG' | translate}}"
key="id"
sortable="true"
class="full-width ellipsis-cell">
<template let-entry="$implicit">
<alfresco-tag-node-list [nodeId]="entry.data.getValue(entry.row, entry.col)"></alfresco-tag-node-list>
</template>
</content-column>
```
![Tag component in document List](docs/assets/document-list-tag-template.png)
### Actions

Some files were not shown because too many files have changed in this diff Show More