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

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. -->

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?**

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
@@ -26,7 +26,7 @@ npm install
npm start
```
This command compiles and starts the project in watch mode.
This command compiles and starts the project in watch mode.
Browser will automatically reload upon changes.
Upon start you can navigate to `http://localhost:3000` with your preferred browser.
@@ -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'

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

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">

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();
}

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 { }

View File

@@ -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>

View File

@@ -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();

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -23,6 +23,7 @@
.toggle {
width: 120px;
margin: 20px;
padding: 5px;
}
@media (min-width: 721px) {

View File

@@ -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>

View File

@@ -17,7 +17,6 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Component({
selector: 'search-component',

View File

@@ -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>

View File

@@ -62,3 +62,4 @@ if (process.env.ENV === 'production') {
}
require('pdfjs-dist/web/pdf_viewer.js');
require('three/build/three.min.js');

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({

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
{

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
{

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',

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

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",

View File

@@ -4,6 +4,7 @@
"DOCUMENT_LIST": {
"COLUMNS": {
"DISPLAY_NAME": "Display name",
"TAG": "Tag",
"CREATED_BY": "Created by",
"CREATED_ON": "Created on"
},

View File

@@ -4,6 +4,7 @@
"DOCUMENT_LIST": {
"COLUMNS": {
"DISPLAY_NAME": "Mostra name",
"TAG": "Tag",
"CREATED_BY": "Creato da",
"CREATED_ON": "Creato il"
},

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,

View File

@@ -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:

View File

@@ -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

View File

@@ -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",

View File

@@ -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(

View File

@@ -3,3 +3,8 @@
.analytics-row__entry {
cursor: pointer;
}
.report-icons {
margin: 20px 20px 20px 20px;
float: right;
}

View File

@@ -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>

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -16,4 +16,37 @@
.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;
}

View File

@@ -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>
<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>

View File

@@ -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);
});
});
});

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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'
});
}));
});
});
});

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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":""
}
}

View File

@@ -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": {

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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');

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

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

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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', () => {

View File

@@ -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) {

View File

@@ -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: [

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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,

View File

@@ -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;

View File

@@ -2,6 +2,9 @@
"FORM": {
"START_FORM": {
"TITLE": "Start Form"
},
"PREVIEW": {
"IMAGE_NOT_AVAILABLE": "The preview image is not available."
}
}
}

View File

@@ -0,0 +1,10 @@
{
"FORM": {
"START_FORM": {
"TITLE": "Inizia"
},
"PREVIEW": {
"IMAGE_NOT_AVAILABLE": "Anteprima immagine non disponibile."
}
}
}

View File

@@ -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';

View File

@@ -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));

View File

@@ -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 {

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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(() => {

View File

@@ -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(() => {

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)

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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);
}
);
}
}
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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>

View File

@@ -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', () => {

View File

@@ -44,6 +44,9 @@ export class DataTableComponent implements OnInit {
@Input()
actions: boolean = false;
@Input()
actionsPosition: string = 'right'; // left|right
@Input()
fallbackThumbnail: string;

View File

@@ -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