diff --git a/.travis.yml b/.travis.yml index 38bd43820e..871656636c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,6 @@ before_install: - export CHROME_BIN=/usr/bin/google-chrome - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - - (cd ng2-components/ng2-alfresco-core; npm version patch; sed -i "s/0\\.0\\.0-PLACEHOLDER/^0.1.0/g" package.json; npm install; npm link) - env: matrix: - MODULE=ng2-alfresco-core @@ -28,11 +26,56 @@ env: - MODULE=ng2-alfresco-search - MODULE=ng2-alfresco-upload - MODULE=ng2-alfresco-viewer + - MODULE=ng2-alfresco-webscript + - MODULE=ng2-activiti-form + - MODULE=ng2-activiti-tasklist + - MODULE=ng2-activiti-processlist before_script: - - cd ng2-components/$MODULE; npm version patch; sed -i "s/0\\.0\\.0-PLACEHOLDER/^0.1.0/g" package.json; npm install; npm link ng2-alfresco-core; npm run travis + - if ([ "$MODULE" != "ng2-alfresco-core" ]); then + (cd ng2-components/ng2-alfresco-core; npm install; npm link); + fi + - if ([ "$MODULE" == "ng2-alfresco-documentlist" ] || [ "$MODULE" == "ng2-alfresco-webscript" ] || [ "$MODULE" == "ng2-activiti-processlist" ] || [ "$MODULE" == "ng2-activiti-tasklist" ]); then + (cd ng2-components/ng2-alfresco-datatable; npm link ng2-alfresco-core; npm install; npm link); + fi + - if ([ "$MODULE" == "ng2-activiti-tasklist" ] || [ "$MODULE" == "ng2-activiti-processlist" ]); then + (cd ng2-components/ng2-activiti-form; npm link ng2-alfresco-core; npm install; npm link); + fi + - if ([ "$MODULE" == "ng2-activiti-processlist" ]); then + (cd ng2-components/ng2-activiti-tasklist; npm link ng2-alfresco-core; npm link ng2-alfresco-datatable; npm link ng2-activiti-form; npm install; npm link); + fi + - cd ng2-components/$MODULE; + - if ([ "$MODULE" != "ng2-alfresco-core" ]); then + npm link ng2-alfresco-core; + fi + - if ([ "$MODULE" == "ng2-alfresco-documentlist" ] || [ "$MODULE" == "ng2-alfresco-webscript" ] || [ "$MODULE" == "ng2-activiti-processlist" ] || [ "$MODULE" == "ng2-activiti-tasklist" ]); then + npm link ng2-alfresco-datatable; + fi + - if ([ "$MODULE" == "ng2-activiti-tasklist" ]); then + npm link ng2-activiti-form; + fi + - if ([ "$MODULE" == "ng2-activiti-processlist" ]); then + npm link ng2-activiti-tasklist; + fi + - npm install; + - npm run travis - ls -ltrh ./node_modules/ script: npm run test # Send coverage data to Coveralls after_success: - - "cat ./coverage/report/lcov.info | ./node_modules/coveralls/bin/coveralls.js --verbose" + - bash <(curl -s https://codecov.io/bash) + +cache: + directories: + - demo-shell-ng2/node_modules + - ng2-components/ng2-activiti-form/node_modules + - ng2-components/ng2-activiti-processlist/node_modules + - ng2-components/ng2-activiti-tasklist/node_modules + - ng2-components/ng2-alfresco-core/node_modules + - ng2-components/ng2-alfresco-datatable/node_modules + - ng2-components/ng2-alfresco-documentlist/node_modules + - ng2-components/ng2-alfresco-login/node_modules + - ng2-components/ng2-alfresco-search/node_modules + - ng2-components/ng2-alfresco-upload/node_modules + - ng2-components/ng2-alfresco-viewer/node_modules + - ng2-components/ng2-alfresco-webscript/node_modules diff --git a/PREREQUISITES.md b/PREREQUISITES.md index a14302fa9a..7687ee6ec4 100644 --- a/PREREQUISITES.md +++ b/PREREQUISITES.md @@ -2,9 +2,13 @@ The [Angular 2](https://angular.io/) based application development framework requires the following: -- An Alfresco Platform Repository to talk to, which has [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) enabled. +- An Alfresco Platform Repository (version [5.2.a-EA](https://wiki.alfresco.com/wiki/Community_file_list_201606-EA) or newer) to talk to, which has [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) enabled. - [Node.js](https://nodejs.org/en/) JavaScript runtime. - [npm](https://www.npmjs.com/) package manager for JavaScript. + +**Verify that you are running at least node `v5.x.x` and npm `3.x.x`** +by running `node -v` and `npm -v` in a terminal/console window. +Older versions produce errors. ## Installing Alfresco @@ -25,9 +29,18 @@ The web client that we are building with the application development framework w So we need to tell the Alfresco server that any request that comes in from this custom web client should be allowed access to the Content Repository. This is done by enabling CORS. -To enable CORS in the Alfresco Platform do the following: +To enable CORS in the Alfresco Platform do one of the following: -Modify *tomcat/webapps/alfresco/WEB-INF/web.xml* and uncomment the following section and update +**Download and install the enable CORS module** + +This is the easiest way, add the [enablecors](https://artifacts.alfresco.com/nexus/service/local/repositories/releases/content/org/alfresco/enablecors/1.0/enablecors-1.0.jar) +platform module JAR to the *$ALF_INSTALL_DIR/modules/platform* directory and restart the server. + +Note. by default the CORS filter that is enabled will allow any orgin. + +**Manually update the web.xml file** + +Modify *$ALF_INSTALL_DIR/tomcat/webapps/alfresco/WEB-INF/web.xml* and uncomment the following section and update `cors.allowOrigin` to `http://localhost:3000`: ``` diff --git a/README.md b/README.md index a087e86b10..18ef46855f 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,16 @@ [![Join the chat at https://gitter.im/Alfresco/alfresco-ng2-components](https://badges.gitter.im/Alfresco/alfresco-ng2-components.svg)](https://gitter.im/Alfresco/alfresco-ng2-components?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

- + travis
     Status - - Coverage Status + + travis
+    Status + + + Coverage Status license @@ -55,6 +59,7 @@ The following is a list of some of the components that you can use when building - [Viewer](ng2-components/ng2-alfresco-viewer/README.md) - [Login](ng2-components/ng2-alfresco-login/README.md) - [Upload](ng2-components/ng2-alfresco-upload/README.md) +- [Webscript viewer](ng2-components/ng2-alfresco-webscript/README.md) You can browse all the components at the following [page](http://devproducts.alfresco.com/). @@ -81,4 +86,13 @@ To generate your Alfresco Angular 2 application you can use the following Yeoman - [Yeoman Generator Angular 2 Alfresco application](https://github.com/Alfresco/generator-ng2-alfresco-app) +## Amazon AWS Elastic Beanstalk fast deploy + +

+ AmazonWebservices Logo.svg +

+ +To deploy directly on your AWS instance our demo shell click the button below: + + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..6487d8bafb --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,53 @@ +# Test against this version of Node.js +branches: + only: + - master + +environment: + nodejs_version: "5" + + matrix: + - COMPONENT_NAME: ng2-alfresco-core + - COMPONENT_NAME: ng2-alfresco-datatable + - COMPONENT_NAME: ng2-alfresco-documentlist + - COMPONENT_NAME: ng2-alfresco-login + - COMPONENT_NAME: ng2-alfresco-search + - COMPONENT_NAME: ng2-alfresco-upload + - COMPONENT_NAME: ng2-alfresco-viewer + - COMPONENT_NAME: ng2-alfresco-webscript + - COMPONENT_NAME: ng2-activiti-form + - COMPONENT_NAME: ng2-activiti-tasklist + - COMPONENT_NAME: ng2-activiti-processlist + +# Install scripts. (runs after repo cloning) +install: + # Get the latest stable version of Node.js or io.js + - ps: Install-Product node $env:nodejs_version + # install module + - IF %COMPONENT_NAME% NEQ ng2-alfresco-core (cd ng2-components/ng2-alfresco-core && npm install && npm link && cd ../../) + - IF %COMPONENT_NAME% EQU ng2-alfresco-documentlist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../) + - IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../) + - IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-activiti-form && npm link ng2-alfresco-core && npm install && npm link && cd ../../) + - IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-activiti-tasklist && npm link ng2-alfresco-core && npm link ng2-alfresco-datatable && npm link ng2-activiti-form && npm install && npm link && cd ../../) + - IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../) + - IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-activiti-form && npm link ng2-alfresco-core && npm install && npm link && cd ../../) + - IF %COMPONENT_NAME% EQU ng2-alfresco-webscript (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../) + - cd ng2-components/%COMPONENT_NAME% + - IF %COMPONENT_NAME% NEQ ng2-alfresco-core (npm link ng2-alfresco-core) + - IF %COMPONENT_NAME% EQU ng2-alfresco-documentlist (npm link ng2-alfresco-datatable) + - IF %COMPONENT_NAME% EQU ng2-activiti-processlist (npm link ng2-alfresco-datatable) + - IF %COMPONENT_NAME% EQU ng2-activiti-processlist (npm link ng2-activiti-tasklist) + - IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (npm link ng2-alfresco-datatable && npm link ng2-activiti-form) + - IF %COMPONENT_NAME% EQU ng2-alfresco-webscript (npm link ng2-alfresco-datatable) + - npm install + +# Post-install test scripts. +test_script: + # Output useful info for debugging. + - node --version + - npm --version + # run tests + - npm test + +# Don't actually build. +build: off diff --git a/demo-shell-ng2/.ebextensions/deploy_npm.config b/demo-shell-ng2/.ebextensions/deploy_npm.config new file mode 100644 index 0000000000..7abdb9c83f --- /dev/null +++ b/demo-shell-ng2/.ebextensions/deploy_npm.config @@ -0,0 +1,18 @@ +files: + "/opt/elasticbeanstalk/hooks/appdeploy/pre/50npm.sh" : + mode: "000775" + owner: root + group: root + content: | + #!/bin/bash + function error_exit + { + eventHelper.py --msg "$1" --severity ERROR + exit $2 + } + + export HOME=/home/ec2-user + echo "export home" + + sudo curl -sL https://rpm.nodesource.com/setup_6.x | sudo -E bash - + sudo yum install -y nodejs diff --git a/demo-shell-ng2/.gitignore b/demo-shell-ng2/.gitignore index fd79f1c9c2..964913e5ef 100644 --- a/demo-shell-ng2/.gitignore +++ b/demo-shell-ng2/.gitignore @@ -4,4 +4,8 @@ bower_components/ app/**/*.js app/**/*.js.map .idea -dist/ \ No newline at end of file +dist/ + +# docker files +docker-compose.yml +Dockerfile diff --git a/demo-shell-ng2/app.js b/demo-shell-ng2/app.js new file mode 100644 index 0000000000..f897a59bcc --- /dev/null +++ b/demo-shell-ng2/app.js @@ -0,0 +1,77 @@ +var port = process.env.PORT || 3000, + http = require('http'), + fs = require('fs'), + url = require('url'), + mime = require('mime'), + html = fs.readFileSync('index.html'); + +var log = function(entry) { + fs.appendFileSync('/tmp/sample-app.log', new Date().toISOString() + ' - ' + entry + '\n'); +}; + +var server = http.createServer(function (req, res) { + + // Parse the request containing file name + var pathname = url.parse(req.url).pathname; + + // Print the name of the file for which request is made. + console.log("Request for " + pathname + " received."); + + if (req.method === 'POST') { + var body = ''; + + req.on('data', function(chunk) { + body += chunk; + }); + + req.on('end', function() { + if (req.url === '/') { + log('Received message: ' + body); + } else if (req.url = '/scheduled') { + log('Received task ' + req.headers['x-aws-sqsd-taskname'] + ' scheduled at ' + req.headers['x-aws-sqsd-scheduled-at']); + } + + res.writeHead(200, 'OK', {'Content-Type': 'text/plain'}); + res.end(); + }); + } else { + var filename = pathname.substr(1); + if(filename === '' || filename.indexOf('.') === -1){ + filename = 'index.html'; + } + // Read the requested file content from file system + fs.readFile(filename, function (err, data) { + if (err) { + console.log(err, filename); + // HTTP Status: 404 : NOT FOUND + // Content Type: text/plain + res.writeHead(404, {'Content-Type': 'text/html'}); + }else{ + //Page found + // HTTP Status: 200 : OK + // Content Type: text/plain + var type = mime.lookup(filename); + console.log('type',type); + if (!res.getHeader('content-type')) { + var charset = mime.charsets.lookup(type); + res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); + } + if(type.indexOf('image') > -1 || type.indexOf('font') > -1){ + var img = fs.readFileSync(pathname.substr(1)); + res.end(img, 'binary'); + }else{ + res.write(data.toString()); + } + // Write the content of the file to response body + } + // Send the response body + res.end(); + }); + } +}); + +// Listen on port 3000, IP defaults to 127.0.0.1 +server.listen(port); + +// Put a friendly message on the terminal +console.log('Server running at http://127.0.0.1:' + port + '/'); diff --git a/demo-shell-ng2/app/app.component.html b/demo-shell-ng2/app/app.component.html index 0447a85c1c..c5d2a1d1e7 100644 --- a/demo-shell-ng2/app/app.component.html +++ b/demo-shell-ng2/app/app.component.html @@ -12,11 +12,13 @@ @@ -30,8 +32,8 @@
  • English
  • Greek
  • Italian
  • -
  • More
  • -
  • Login
  • +
  • More
  • + Login
  • Logout
  • @@ -39,18 +41,29 @@
    Components List + ECM host + + BPM host +
    -
    diff --git a/demo-shell-ng2/app/app.component.ts b/demo-shell-ng2/app/app.component.ts index f39e2c25cf..415c37fcc2 100644 --- a/demo-shell-ng2/app/app.component.ts +++ b/demo-shell-ng2/app/app.component.ts @@ -16,8 +16,7 @@ */ import { Component } from '@angular/core'; -import { Router, RouteConfig, ROUTER_DIRECTIVES } from '@angular/router-deprecated'; -import { FilesComponent } from './components/files/files.component'; +import { ROUTER_DIRECTIVES, Router } from '@angular/router'; import { MDL, @@ -26,12 +25,8 @@ import { AlfrescoPipeTranslate, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; -import { UploadButtonComponent } from 'ng2-alfresco-upload'; -import { DataTableDemoComponent } from './components/datatable/datatable-demo.component'; -import { SearchComponent } from './components/search/search.component'; -import { SearchBarComponent } from './components/search/search-bar.component'; -import { LoginDemoComponent } from './components/login/login-demo.component'; -import { TasksDemoComponent } from './components/tasks/tasks-demo.component'; + +import { SearchBarComponent } from './components/index'; declare var document: any; @@ -42,32 +37,36 @@ declare var document: any; directives: [SearchBarComponent, ROUTER_DIRECTIVES, MDL], pipes: [AlfrescoPipeTranslate] }) -@RouteConfig([ - {path: '/home', name: 'Home', component: FilesComponent}, - {path: '/files', name: 'Files', component: FilesComponent}, - {path: '/datatable', name: 'DataTable', component: DataTableDemoComponent}, - {path: '/', name: 'Login', component: LoginDemoComponent, useAsDefault: true}, - {path: '/uploader', name: 'Uploader', component: UploadButtonComponent}, - {path: '/login', name: 'Login', component: LoginDemoComponent}, - {path: '/search', name: 'Search', component: SearchComponent}, - {path: '/tasks', name: 'Tasks', component: TasksDemoComponent} -]) export class AppComponent { translate: AlfrescoTranslationService; searchTerm: string = ''; + ecmHost: string = 'http://' + window.location.hostname + ':8080'; + bpmHost: string = 'http://' + window.location.hostname + ':9999'; + constructor(public auth: AlfrescoAuthenticationService, public router: Router, translate: AlfrescoTranslationService, - alfrescoSettingsService: AlfrescoSettingsService) { - alfrescoSettingsService.host = 'http://localhost:8080'; + public alfrescoSettingsService: AlfrescoSettingsService) { + this.setEcmHost(); + this.setBpmHost(); this.translate = translate; this.translate.addTranslationFolder(); } - isActive(instruction: any[]): boolean { - return this.router.isRouteActive(this.router.generate(instruction)); + public onChangeECMHost(event: KeyboardEvent): void { + console.log((event.target).value); + this.ecmHost = (event.target).value; + this.alfrescoSettingsService.ecmHost = this.ecmHost; + localStorage.setItem(`ecmHost`, this.ecmHost); + } + + public onChangeBPMHost(event: KeyboardEvent): void { + console.log((event.target).value); + this.bpmHost = (event.target).value; + this.alfrescoSettingsService.bpmHost = this.bpmHost; + localStorage.setItem(`bpmHost`, this.bpmHost); } isLoggedIn(): boolean { @@ -78,7 +77,7 @@ export class AppComponent { event.preventDefault(); this.auth.logout() .subscribe( - () => this.router.navigate(['Login']) + () => this.router.navigate(['/login']) ); } @@ -100,4 +99,22 @@ export class AppComponent { // todo: workaround for drawer closing document.querySelector('.mdl-layout').MaterialLayout.toggleDrawer(); } + + private setEcmHost() { + if (localStorage.getItem(`ecmHost`)) { + this.alfrescoSettingsService.ecmHost = localStorage.getItem(`ecmHost`); + this.ecmHost = localStorage.getItem(`ecmHost`); + } else { + this.alfrescoSettingsService.ecmHost = this.ecmHost; + } + } + + private setBpmHost() { + if (localStorage.getItem(`bpmHost`)) { + this.alfrescoSettingsService.bpmHost = localStorage.getItem(`bpmHost`); + this.bpmHost = localStorage.getItem(`bpmHost`); + } else { + this.alfrescoSettingsService.bpmHost = this.bpmHost; + } + } } diff --git a/demo-shell-ng2/app/app.routes.ts b/demo-shell-ng2/app/app.routes.ts new file mode 100644 index 0000000000..feaa94f7c0 --- /dev/null +++ b/demo-shell-ng2/app/app.routes.ts @@ -0,0 +1,50 @@ +/*! + * @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 { provideRouter, RouterConfig } from '@angular/router'; + +import { + FilesComponent, + UploadButtonComponent, + DataTableDemoComponent, + SearchComponent, + LoginDemoComponent, + ActivitiDemoComponent, + WebscriptComponent, + AboutComponent, + FormViewer +} from './components/index'; +import { FormNodeViewer } from './components/activiti/form-node-viewer.component'; + +export const routes: RouterConfig = [ + { path: 'home', component: FilesComponent }, + { path: 'files', component: FilesComponent }, + { path: 'datatable', component: DataTableDemoComponent }, + { path: '', component: LoginDemoComponent }, + { path: 'uploader', component: UploadButtonComponent }, + { path: 'login', component: LoginDemoComponent }, + { path: 'search', component: SearchComponent }, + { path: 'activiti', component: ActivitiDemoComponent }, + { path: 'activiti/tasks/:id', component: FormViewer }, + { path: 'activiti/tasksnode/:id', component: FormNodeViewer }, + { path: 'webscript', component: WebscriptComponent }, + { path: 'about', component: AboutComponent } +]; + +export const appRouterProviders = [ + provideRouter(routes) +]; diff --git a/demo-shell-ng2/app/components/about/about.component.html b/demo-shell-ng2/app/components/about/about.component.html new file mode 100644 index 0000000000..c0922f8141 --- /dev/null +++ b/demo-shell-ng2/app/components/about/about.component.html @@ -0,0 +1,4 @@ +
    +

    Packages

    + +
    diff --git a/demo-shell-ng2/app/components/about/about.component.ts b/demo-shell-ng2/app/components/about/about.component.ts new file mode 100644 index 0000000000..30b667a14a --- /dev/null +++ b/demo-shell-ng2/app/components/about/about.component.ts @@ -0,0 +1,55 @@ +/*! + * @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, OnInit } from '@angular/core'; +import { Http } from '@angular/http'; +import { + ALFRESCO_DATATABLE_DIRECTIVES, + ObjectDataTableAdapter /*, + DataSorting, + ObjectDataRow, + ObjectDataColumn*/ +} from 'ng2-alfresco-datatable'; + +declare let __moduleName: string; + +@Component({ + moduleId: __moduleName, + selector: 'about-page', + templateUrl: './about.component.html', + directives: [ALFRESCO_DATATABLE_DIRECTIVES] +}) +export class AboutComponent implements OnInit { + + data: ObjectDataTableAdapter; + + constructor(private http: Http) {} + + ngOnInit() { + // this.data = new ObjectDataTableAdapter(); + this.http.get('/versions').subscribe(response => { + let data = response.json() || {}; + let packages = data.packages || []; + + this.data = new ObjectDataTableAdapter(packages, [ + { type: 'text', key: 'name', title: 'Name', sortable: true }, + { type: 'text', key: 'version', title: 'Version', sortable: true } + ]); + }); + + } +} diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.css b/demo-shell-ng2/app/components/activiti/activiti-demo.component.css new file mode 100644 index 0000000000..88261c858f --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.css @@ -0,0 +1,14 @@ +.activiti-task-list__item:hover { + cursor: pointer; + font-weight: bold; +} + +.activiti { + background-color: #f5f5f5; +} + +.task-column { + background-color: #f5f5f5; + padding: 10px 10px 10px 10px; + border: solid 2px rgb(31,188,210); +} diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.html b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html new file mode 100644 index 0000000000..988b262764 --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html @@ -0,0 +1,65 @@ +
    +
    + + +
    +
    +
    +
    +
    +
    + Task Filters + +
    +
    + Task List + +
    +
    + Task Details + +
    +
    +
    +
    +
    +
    +
    +
    +
    + Process Filters + + +
    +
    + Process List + +
    +
    + Process Details + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts b/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts new file mode 100644 index 0000000000..b30933a0c0 --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts @@ -0,0 +1,119 @@ +/*! + * @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, AfterViewChecked, ViewChild, Input } from '@angular/core'; +import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist'; +import { ACTIVITI_PROCESSLIST_DIRECTIVES } from 'ng2-activiti-processlist'; +import { ActivitiForm } from 'ng2-activiti-form'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'activiti-demo', + templateUrl: './activiti-demo.component.html', + styleUrls: ['./activiti-demo.component.css'], + directives: [ALFRESCO_TASKLIST_DIRECTIVES, ACTIVITI_PROCESSLIST_DIRECTIVES, ActivitiForm] +}) +export class ActivitiDemoComponent implements AfterViewChecked { + + currentChoice: string = 'task-list'; + + @ViewChild('activitidetails') + activitidetails: any; + + @ViewChild('activititasklist') + activititasklist: any; + + @ViewChild('activitiprocesslist') + activitiprocesslist: any; + + @ViewChild('activitiprocessdetails') + activitiprocessdetails: any; + + currentTaskId: string; + currentProcessInstanceId: string; + + taskSchemaColumns: any [] = []; + processSchemaColumns: any [] = []; + + taskFilter: any; + processFilter: any; + + @Input() + appId: string; + + setChoice($event) { + this.currentChoice = $event.target.value; + } + + isProcessListSelected() { + return this.currentChoice === 'process-list'; + } + + isTaskListSelected() { + return this.currentChoice === 'task-list'; + } + + constructor() { + this.taskSchemaColumns = [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true} + // {type: 'text', key: 'created', title: 'Created', sortable: true} + ]; + this.processSchemaColumns = [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true} + ]; + } + + onTaskFilterClick(event: any) { + this.taskFilter = event; + this.activititasklist.load(this.taskFilter); + } + + onProcessFilterClick(event: any) { + this.processFilter = event.filter; + this.activitiprocesslist.load(this.processFilter); + } + + onTaskRowClick(taskId) { + this.currentTaskId = taskId; + this.activitidetails.loadDetails(this.currentTaskId); + } + + onProcessRowClick(processInstanceId) { + this.currentProcessInstanceId = processInstanceId; + this.activitiprocessdetails.load(this.currentProcessInstanceId); + } + + processCancelled(data: any) { + this.currentProcessInstanceId = null; + this.activitiprocesslist.reload(); + } + + taskFormCompleted(data: any) { + this.activitiprocesslist.reload(); + } + + ngAfterViewChecked() { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + } + } + +} diff --git a/demo-shell-ng2/app/components/activiti/form-node-viewer.component.css b/demo-shell-ng2/app/components/activiti/form-node-viewer.component.css new file mode 100644 index 0000000000..0e5cdfdd65 --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/form-node-viewer.component.css @@ -0,0 +1,3 @@ +.activiti-form-viewer { + margin: 10px; +} diff --git a/demo-shell-ng2/app/components/activiti/form-node-viewer.component.html b/demo-shell-ng2/app/components/activiti/form-node-viewer.component.html new file mode 100644 index 0000000000..e2f166282b --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/form-node-viewer.component.html @@ -0,0 +1,6 @@ +
    + + +
    diff --git a/demo-shell-ng2/app/components/activiti/form-node-viewer.component.ts b/demo-shell-ng2/app/components/activiti/form-node-viewer.component.ts new file mode 100644 index 0000000000..1b5f994660 --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/form-node-viewer.component.ts @@ -0,0 +1,62 @@ +/*! + * @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, OnInit, OnDestroy, AfterViewChecked } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form'; +import { Subscription } from 'rxjs/Rx'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'form-node-viewer', + templateUrl: './form-node-viewer.component.html', + styleUrls: ['./form-node-viewer.component.css'], + directives: [ActivitiForm], + providers: [FormService, EcmModelService, NodeService] +}) +export class FormNodeViewer implements OnInit, OnDestroy, AfterViewChecked { + + nodeId: string; + + private sub: Subscription; + + constructor(private formService: FormService, + private route: ActivatedRoute, + private router: Router) { + } + + ngOnInit() { + this.sub = this.route.params.subscribe(params => { + this.nodeId = params['id']; + }); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + } + + ngAfterViewChecked() { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + } + } + +} diff --git a/demo-shell-ng2/app/components/activiti/form-viewer.component.css b/demo-shell-ng2/app/components/activiti/form-viewer.component.css new file mode 100644 index 0000000000..0e5cdfdd65 --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/form-viewer.component.css @@ -0,0 +1,3 @@ +.activiti-form-viewer { + margin: 10px; +} diff --git a/demo-shell-ng2/app/components/activiti/form-viewer.component.html b/demo-shell-ng2/app/components/activiti/form-viewer.component.html new file mode 100644 index 0000000000..2a5c98ccdb --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/form-viewer.component.html @@ -0,0 +1,11 @@ +
    + + + + + + + + + +
    diff --git a/demo-shell-ng2/app/components/activiti/form-viewer.component.ts b/demo-shell-ng2/app/components/activiti/form-viewer.component.ts new file mode 100644 index 0000000000..2acfc75777 --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/form-viewer.component.ts @@ -0,0 +1,62 @@ +/*! + * @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, OnInit, OnDestroy, AfterViewChecked } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form'; +import { Subscription } from 'rxjs/Rx'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'form-viewer', + templateUrl: './form-viewer.component.html', + styleUrls: ['./form-viewer.component.css'], + directives: [ActivitiForm], + providers: [FormService, EcmModelService, NodeService] +}) +export class FormViewer implements OnInit, OnDestroy, AfterViewChecked { + + taskId: string; + + private sub: Subscription; + + constructor(private formService: FormService, + private route: ActivatedRoute, + private router: Router) { + } + + ngOnInit() { + this.sub = this.route.params.subscribe(params => { + this.taskId = params['id']; + }); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + } + + ngAfterViewChecked() { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + } + } + +} diff --git a/demo-shell-ng2/app/components/files/files.component.css b/demo-shell-ng2/app/components/files/files.component.css index e69de29bb2..c2bac0048e 100644 --- a/demo-shell-ng2/app/components/files/files.component.css +++ b/demo-shell-ng2/app/components/files/files.component.css @@ -0,0 +1,9 @@ +.container { + margin: 10px; +} + +@media only screen and (max-width: 640px) { + .container { + margin: 0; + } +} diff --git a/demo-shell-ng2/app/components/files/files.component.html b/demo-shell-ng2/app/components/files/files.component.html index 4b85525635..fabeb89aef 100644 --- a/demo-shell-ng2/app/components/files/files.component.html +++ b/demo-shell-ng2/app/components/files/files.component.html @@ -1,118 +1,109 @@ - - - - + - - - - - + [uploaddirectory]="" + [versioning] = "versioning" + (onSuccess)="documentList.reload()"> + + + - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -135,37 +126,70 @@ +

    + +

    -
    Single file upload
    - - -
    Folder upload
    - - +

    + +

    -
    Multiple file upload
    - - - +

    + +

    + +

    + +

    + +
    Upload
    +
    +
    + Extension accepted + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + diff --git a/demo-shell-ng2/app/components/files/files.component.ts b/demo-shell-ng2/app/components/files/files.component.ts index d10605b54e..c6d8e3fad9 100644 --- a/demo-shell-ng2/app/components/files/files.component.ts +++ b/demo-shell-ng2/app/components/files/files.component.ts @@ -15,11 +15,16 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; import { DOCUMENT_LIST_DIRECTIVES, DOCUMENT_LIST_PROVIDERS, - DocumentActionsService + DocumentActionsService, + DocumentList, + ContentActionHandler, + DocumentActionModel, + FolderActionModel } from 'ng2-alfresco-documentlist'; import { MDL, @@ -27,8 +32,10 @@ import { CONTEXT_MENU_DIRECTIVES, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { PaginationComponent } from 'ng2-alfresco-datatable'; import { ALFRESCO_ULPOAD_COMPONENTS } from 'ng2-alfresco-upload'; import { VIEWERCOMPONENT } from 'ng2-alfresco-viewer'; +import { FormService } from 'ng2-activiti-form'; declare let __moduleName: string; @@ -42,24 +49,31 @@ declare let __moduleName: string; MDL, ALFRESCO_ULPOAD_COMPONENTS, VIEWERCOMPONENT, - CONTEXT_MENU_DIRECTIVES + CONTEXT_MENU_DIRECTIVES, + PaginationComponent ], - providers: [DOCUMENT_LIST_PROVIDERS], + providers: [DOCUMENT_LIST_PROVIDERS, FormService], pipes: [AlfrescoPipeTranslate] }) -export class FilesComponent { +export class FilesComponent implements OnInit { currentPath: string = '/Sites/swsdp/documentLibrary'; - urlFile: string; - fileName: string; - mimeType: string; + fileNodeId: any; fileShowed: boolean = false; + multipleFileUpload: boolean = false; + folderUpload: boolean = false; + acceptedFilesTypeShow: boolean = false; + versioning: boolean = false; acceptedFilesType: string = '.jpg,.pdf,.js'; - constructor( - private contentService: AlfrescoContentService, - documentActions: DocumentActionsService) { + @ViewChild(DocumentList) + documentList: DocumentList; + + constructor(private contentService: AlfrescoContentService, + private documentActions: DocumentActionsService, + private formService: FormService, + private router: Router) { documentActions.setHandler('my-handler', this.myDocumentActionHandler.bind(this)); } @@ -77,9 +91,7 @@ export class FilesComponent { showFile(event) { if (event.value.entry.isFile) { - this.fileName = event.value.entry.name; - this.mimeType = event.value.entry.content.mimeType; - this.urlFile = this.contentService.getContentUrl(event.value); + this.fileNodeId = event.value.entry.id; this.fileShowed = true; } else { this.fileShowed = false; @@ -91,4 +103,56 @@ export class FilesComponent { this.currentPath = event.path; } } + + toggleMultipleFileUpload() { + this.multipleFileUpload = !this.multipleFileUpload; + return this.multipleFileUpload; + } + + toggleFolder() { + this.multipleFileUpload = false; + this.folderUpload = !this.folderUpload; + return this.folderUpload; + } + + toggleAcceptedFilesType() { + this.acceptedFilesTypeShow = !this.acceptedFilesTypeShow; + return this.acceptedFilesTypeShow; + } + + toggleVersioning() { + this.versioning = !this.versioning; + return this.versioning; + } + + ngOnInit() { + this.formService.getProcessDefinitions().subscribe( + defs => this.setupBpmActions(defs || []), + err => console.log(err) + ); + } + + viewActivitiForm(event?: any) { + this.router.navigate(['/activiti/tasksnode', event.value.entry.id]); + } + + private setupBpmActions(actions: any[]) { + actions.map(def => { + let documentAction = new DocumentActionModel(); + documentAction.title = 'Activiti: ' + (def.name || 'Unknown process'); + documentAction.handler = this.getBpmActionHandler(def); + this.documentList.actions.push(documentAction); + + let folderAction = new FolderActionModel(); + folderAction.title = 'Activiti: ' + (def.name || 'Unknown process'); + folderAction.handler = this.getBpmActionHandler(def); + this.documentList.actions.push(folderAction); + }); + } + + private getBpmActionHandler(processDefinition: any): ContentActionHandler { + return function (obj: any, target?: any) { + window.alert(`Starting BPM process: ${processDefinition.id}`); + }.bind(this); + } } diff --git a/demo-shell-ng2/app/components/index.ts b/demo-shell-ng2/app/components/index.ts new file mode 100644 index 0000000000..368d7ea43e --- /dev/null +++ b/demo-shell-ng2/app/components/index.ts @@ -0,0 +1,27 @@ +/*! + * @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 { UploadButtonComponent } from 'ng2-alfresco-upload'; +export { DataTableDemoComponent } from './datatable/datatable-demo.component'; +export { SearchComponent } from './search/search.component'; +export { SearchBarComponent } from './search/search-bar.component'; +export { LoginDemoComponent } from './login/login-demo.component'; +export { ActivitiDemoComponent } from './activiti/activiti-demo.component'; +export { FormViewer } from './activiti/form-viewer.component'; +export { WebscriptComponent } from './webscript/webscript.component'; +export { AboutComponent } from './about/about.component'; +export { FilesComponent } from './files/files.component'; diff --git a/demo-shell-ng2/app/components/login/login-demo.component.html b/demo-shell-ng2/app/components/login/login-demo.component.html index e9fe2b6346..090f4c3f4f 100644 --- a/demo-shell-ng2/app/components/login/login-demo.component.html +++ b/demo-shell-ng2/app/components/login/login-demo.component.html @@ -1 +1,15 @@ - +
    +

    + +

    +

    + +

    +
    + diff --git a/demo-shell-ng2/app/components/login/login-demo.component.ts b/demo-shell-ng2/app/components/login/login-demo.component.ts index 2ccc1f45ce..e7533e7794 100644 --- a/demo-shell-ng2/app/components/login/login-demo.component.ts +++ b/demo-shell-ng2/app/components/login/login-demo.component.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { Component } from '@angular/core'; -import { AlfrescoLoginComponent } from 'ng2-alfresco-login'; -import { Router, ROUTER_DIRECTIVES } from '@angular/router-deprecated'; +import {Component} from '@angular/core'; +import {AlfrescoLoginComponent} from 'ng2-alfresco-login'; +import {ROUTER_DIRECTIVES, Router} from '@angular/router'; declare let __moduleName: string; @@ -30,16 +30,37 @@ declare let __moduleName: string; }) export class LoginDemoComponent { + providers: string = 'ECM'; + constructor(public router: Router) { } onLogin($event) { console.log($event); - this.router.navigate(['Home']); + this.router.navigate(['/home']); } onError($event) { console.log($event); } + toggleECM(checked) { + if (checked && this.providers === 'BPM') { + this.providers = 'ALL'; + } else if (checked) { + this.providers = 'ECM'; + } else { + this.providers = undefined; + } + } + + toggleBPM(checked) { + if (checked && this.providers === 'ECM') { + this.providers = 'ALL'; + } else if (checked) { + this.providers = 'BPM'; + } else { + this.providers = undefined; + } + } } diff --git a/demo-shell-ng2/app/components/search/search-bar.component.html b/demo-shell-ng2/app/components/search/search-bar.component.html index 0d144a574b..4d89fd0d8b 100644 --- a/demo-shell-ng2/app/components/search/search-bar.component.html +++ b/demo-shell-ng2/app/components/search/search-bar.component.html @@ -1,6 +1,8 @@ - +
    diff --git a/demo-shell-ng2/app/components/search/search-bar.component.ts b/demo-shell-ng2/app/components/search/search-bar.component.ts index 9b7704815e..a55f630e1c 100644 --- a/demo-shell-ng2/app/components/search/search-bar.component.ts +++ b/demo-shell-ng2/app/components/search/search-bar.component.ts @@ -16,7 +16,7 @@ */ import { Component, EventEmitter, Output } from '@angular/core'; -import { Router } from '@angular/router-deprecated'; +import { Router } from '@angular/router'; import { ALFRESCO_SEARCH_DIRECTIVES } from 'ng2-alfresco-search'; import { VIEWERCOMPONENT } from 'ng2-alfresco-viewer'; import { diff --git a/demo-shell-ng2/app/components/search/search.component.ts b/demo-shell-ng2/app/components/search/search.component.ts index c3d57964d8..bf836b7783 100644 --- a/demo-shell-ng2/app/components/search/search.component.ts +++ b/demo-shell-ng2/app/components/search/search.component.ts @@ -56,15 +56,14 @@ export class SearchComponent { previewName: string; previewMimeType: string; previewActive: boolean = false; + fileNodeId: string; constructor(public contentService: AlfrescoContentService) { } onFileClicked(event) { if (event.value.entry.isFile) { - this.previewName = event.value.entry.name; - this.previewMimeType = event.value.entry.content.mimeType; - this.previewContentUrl = this.contentService.getContentUrl(event.value); + this.fileNodeId = event.value.entry.id; this.previewActive = true; } } diff --git a/demo-shell-ng2/app/components/tasks/activiti.service.ts b/demo-shell-ng2/app/components/tasks/activiti.service.ts deleted file mode 100644 index 9b8b5d95dc..0000000000 --- a/demo-shell-ng2/app/components/tasks/activiti.service.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * @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 { Injectable } from '@angular/core'; -import { Http, Headers, RequestOptions, Response } from '@angular/http'; -// import { Observable } from 'rxjs/Rx'; - -@Injectable() -export class ActivitiService { - - constructor(private http: Http) {} - - login(username: string, password: string) { - let url = 'http://localhost:9999/activiti-app/app/authentication'; - let headers = new Headers({ - 'Content-Type': 'application/x-www-form-urlencoded', - 'Cache-Control': 'no-cache' - }); - let options = new RequestOptions({ headers: headers }); - let data = 'j_username=' - + encodeURIComponent(username) - + '&j_password=' - + encodeURIComponent(password) - + '&_spring_security_remember_me=true&submit=Login'; - - return this.http - .post(url, data, options) - .toPromise() - // .then(res => console.log(res)) - .catch(this.handleError); - } - - getTasks() { - // emulate filter value - let data = JSON.stringify({ - 'page': 0, - 'filterId': 3, - 'filter': { - 'sort': 'created-desc', - 'name': '', - 'state': 'open', - 'assignment': 'involved' - }, - 'appDefinitionId': null - }); - - let url = 'http://localhost:9999/activiti-app/app/rest/filter/tasks'; - let headers = new Headers({ - 'Content-Type': 'application/json', - 'Cache-Control': 'no-cache' - }); - let options = new RequestOptions({ headers: headers }); - - return this.http - .post(url, data, options) - .toPromise() - .then(this.parseJSON) - .catch(this.handleError); - } - - private parseJSON(res: Response) { - let body = res.json(); - return body.data || { }; - } - - private handleError(error: any) { - console.error('An error occurred', error); - return Promise.reject(error.message || error); - } - -} diff --git a/demo-shell-ng2/app/components/tasks/tasks-demo.component.ts b/demo-shell-ng2/app/components/tasks/tasks-demo.component.ts deleted file mode 100644 index 0b61684eae..0000000000 --- a/demo-shell-ng2/app/components/tasks/tasks-demo.component.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * @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, OnInit } from '@angular/core'; -import { ActivitiService } from './activiti.service'; -import { - ALFRESCO_DATATABLE_DIRECTIVES, - ObjectDataTableAdapter -} from 'ng2-alfresco-datatable'; - -@Component({ - selector: 'tasks-demo', - template: ` -
    - -
    - `, - directives: [ALFRESCO_DATATABLE_DIRECTIVES], - providers: [ActivitiService], - styles: [':host > .container { padding: 10px; }'] -}) -export class TasksDemoComponent implements OnInit { - - tasks: ObjectDataTableAdapter; - - constructor( - private activitiService: ActivitiService) {} - - ngOnInit() { - this.activitiService - .login('denys.vuika@alfresco.com', 'test') - .then(() => { - this.activitiService - .getTasks() - .then((data) => { - let tasks = data || []; - console.log(tasks); - this.loadTasks(tasks); - }); - }); - } - - private loadTasks(tasks: any[]) { - tasks = tasks.map(t => { - t.name = t.name || 'Nameless task'; - if (t.name.length > 50) { - t.name = t.name.substring(0, 50) + '...'; - } - return t; - }); - - this.tasks = new ObjectDataTableAdapter(tasks, [ - { type: 'text', key: 'id', title: 'Id'}, - { type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true }, - { type: 'text', key: 'formKey', title: 'Form Key', sortable: true }, - { type: 'text', key: 'created', title: 'Created', sortable: true } - ]); - } - -} diff --git a/demo-shell-ng2/app/components/webscript/webscript.component.ts b/demo-shell-ng2/app/components/webscript/webscript.component.ts new file mode 100644 index 0000000000..839c42975f --- /dev/null +++ b/demo-shell-ng2/app/components/webscript/webscript.component.ts @@ -0,0 +1,62 @@ +/*! + * @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 } from '@angular/core'; +import { + CONTEXT_MENU_DIRECTIVES +} from 'ng2-alfresco-core'; + +import { WEBSCRIPTCOMPONENT } from 'ng2-alfresco-webscript'; + +@Component({ + selector: 'alfresco-webscript-demo', + template: ` +
    +
    +
    +
    +
    +
    + + `, + directives: [WEBSCRIPTCOMPONENT, CONTEXT_MENU_DIRECTIVES] +}) +export class WebscriptComponent { + + currentPath: string = '/'; + + authenticated: boolean; + + host: string = 'http://127.0.0.1:8080'; + + scriptPath: string = 'sample/folder/Company%20Home'; + + contextRoot: string = 'alfresco'; + + servicePath: string = 'service'; + + scriptArgs: string = ''; + + logData(data) { + console.log(data); + } +} diff --git a/demo-shell-ng2/app/img/admin.jpeg b/demo-shell-ng2/app/img/admin.jpeg new file mode 100644 index 0000000000..0efd7dbd02 Binary files /dev/null and b/demo-shell-ng2/app/img/admin.jpeg differ diff --git a/demo-shell-ng2/app/img/anonymous.gif b/demo-shell-ng2/app/img/anonymous.gif new file mode 100644 index 0000000000..e1b58fa57d Binary files /dev/null and b/demo-shell-ng2/app/img/anonymous.gif differ diff --git a/demo-shell-ng2/app/img/background.jpg b/demo-shell-ng2/app/img/background.jpg new file mode 100644 index 0000000000..792f58a231 Binary files /dev/null and b/demo-shell-ng2/app/img/background.jpg differ diff --git a/demo-shell-ng2/app/img/checklist.svg b/demo-shell-ng2/app/img/checklist.svg new file mode 100644 index 0000000000..603b46fef5 --- /dev/null +++ b/demo-shell-ng2/app/img/checklist.svg @@ -0,0 +1,782 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo-shell-ng2/app/img/completed.svg b/demo-shell-ng2/app/img/completed.svg new file mode 100644 index 0000000000..84f75fa8a2 --- /dev/null +++ b/demo-shell-ng2/app/img/completed.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/demo-shell-ng2/app/img/createvisit.jpg b/demo-shell-ng2/app/img/createvisit.jpg new file mode 100644 index 0000000000..7505e347e4 Binary files /dev/null and b/demo-shell-ng2/app/img/createvisit.jpg differ diff --git a/demo-shell-ng2/app/img/users.png b/demo-shell-ng2/app/img/users.png new file mode 100644 index 0000000000..b1cca051a4 Binary files /dev/null and b/demo-shell-ng2/app/img/users.png differ diff --git a/demo-shell-ng2/app/img/visit.jpg b/demo-shell-ng2/app/img/visit.jpg new file mode 100644 index 0000000000..b07d287404 Binary files /dev/null and b/demo-shell-ng2/app/img/visit.jpg differ diff --git a/demo-shell-ng2/app/img/visitor.jpg b/demo-shell-ng2/app/img/visitor.jpg new file mode 100644 index 0000000000..813426332c Binary files /dev/null and b/demo-shell-ng2/app/img/visitor.jpg differ diff --git a/demo-shell-ng2/app/main.ts b/demo-shell-ng2/app/main.ts index 118d21a748..31c4a652a0 100644 --- a/demo-shell-ng2/app/main.ts +++ b/demo-shell-ng2/app/main.ts @@ -16,17 +16,20 @@ */ import { bootstrap } from '@angular/platform-browser-dynamic'; -import { ROUTER_PROVIDERS } from '@angular/router-deprecated'; import { HTTP_PROVIDERS } from '@angular/http'; import { ALFRESCO_SEARCH_PROVIDERS } from 'ng2-alfresco-search'; import { ALFRESCO_CORE_PROVIDERS } from 'ng2-alfresco-core'; +import { ATIVITI_FORM_PROVIDERS } from 'ng2-activiti-form'; import { UploadService } from 'ng2-alfresco-upload'; import { AppComponent } from './app.component'; +import { appRouterProviders } from './app.routes'; + bootstrap(AppComponent, [ - ROUTER_PROVIDERS, + appRouterProviders, HTTP_PROVIDERS, ALFRESCO_CORE_PROVIDERS, ALFRESCO_SEARCH_PROVIDERS, - UploadService -]); + UploadService, + ATIVITI_FORM_PROVIDERS +]).catch(err => console.error(err)); diff --git a/ng2-components/ng2-alfresco-upload/src/assets/AlfrescoSettingsService.service.mock.ts b/demo-shell-ng2/app/services/notification.service.ts similarity index 50% rename from ng2-components/ng2-alfresco-upload/src/assets/AlfrescoSettingsService.service.mock.ts rename to demo-shell-ng2/app/services/notification.service.ts index fb4a14ca7e..d93a12144b 100644 --- a/ng2-components/ng2-alfresco-upload/src/assets/AlfrescoSettingsService.service.mock.ts +++ b/demo-shell-ng2/app/services/notification.service.ts @@ -16,23 +16,16 @@ */ import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; @Injectable() -export class AlfrescoSettingsServiceMock { +export class NotificationService { - static DEFAULT_HOST_ADDRESS: string = 'fakehost'; - static DEFAULT_CONTEXT_PATH: string = '/fake-path-alfresco'; - static DEFAULT_BASE_API_PATH: string = '/fake-api/fake-public/fake-alfresco/fake-versions/1'; + notificationsdSource = new Subject(); - private _host: string = AlfrescoSettingsServiceMock.DEFAULT_HOST_ADDRESS; - private _contextPath = AlfrescoSettingsServiceMock.DEFAULT_CONTEXT_PATH; - private _apiBasePath: string = AlfrescoSettingsServiceMock.DEFAULT_BASE_API_PATH; + notifications = this.notificationsdSource.asObservable(); - public get host(): string { - return this._host; - } - - getApiBaseUrl(): string { - return this._host + this._contextPath + this._apiBasePath; + sendNotification(message: string) { + this.notificationsdSource.next(message); } } diff --git a/demo-shell-ng2/browser-sync-config.js b/demo-shell-ng2/browser-sync-config.js deleted file mode 100644 index 2d1ab1d7db..0000000000 --- a/demo-shell-ng2/browser-sync-config.js +++ /dev/null @@ -1,24 +0,0 @@ -var browserSync = require("browser-sync").create(); -var historyApiFallback = require('connect-history-api-fallback'); - -browserSync.init({ - - server: { - baseDir: './', - middleware: [ historyApiFallback() ] - }, - - files: [ - 'index.html', - 'app/**/*.{html,htm,css,js}', - 'dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-core/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-datatable/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-documentlist/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-login/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-search/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-upload/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-viewer/dist/**/*.{html,htm,css,js}'], - - reloadDelay: 1000 -}); diff --git a/demo-shell-ng2/i18n/en.json b/demo-shell-ng2/i18n/en.json index feee0cf879..6f006a8158 100644 --- a/demo-shell-ng2/i18n/en.json +++ b/demo-shell-ng2/i18n/en.json @@ -4,7 +4,7 @@ "DOCUMENT_LIST": { "COLUMNS": { "DISPLAY_NAME": "Display name", - "CREATED_BY": "Created by", + "CREATED_BY": "mario", "CREATED_ON": "Created on" }, "ACTIONS": { @@ -39,5 +39,29 @@ "CUSTOM": "Custom action" } } + }, + + "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 4 characters.", + "PASSWORD-REQUIRED": "Enter your password to sign in", + "LOGIN-ERROR": "You have entered an invalid username or password", + "LOGIN-SUCCESS": "Login successful" + }, + "BUTTON": { + "LOGIN": "MARIO" + }, + "ACTION": { + "HELP": "NEED HELP?", + "REGISTER": "REGISTER" + } } } diff --git a/demo-shell-ng2/index.html b/demo-shell-ng2/index.html index ba765707be..3fbdbbf291 100644 --- a/demo-shell-ng2/index.html +++ b/demo-shell-ng2/index.html @@ -24,7 +24,7 @@ - + diff --git a/demo-shell-ng2/package.json b/demo-shell-ng2/package.json index 2b131968f3..5524a4ef30 100644 --- a/demo-shell-ng2/package.json +++ b/demo-shell-ng2/package.json @@ -1,18 +1,19 @@ { "name": "Alfresco-Angular2-Demo", "description": "Demo shell for Alfresco Angular2 components", - "version": "0.2.0", + "version": "0.3.0", "author": "Alfresco Software, Ltd.", "scripts": { + "postinstall": "typings install", "clean": "rimraf node_modules typings", - "start": "npm run tslint && typings install && concurrently \"npm run tsc:w\" \"license-check\" \"npm run serve\" ", + "build": "npm run tslint && npm run tsc && npm run licensecheck", + "start": "npm run build && concurrently \"npm run tsc:w\" \"npm run serve\" ", + "aws": "node app.js", "tsc": "tsc", "tsc:w": "tsc -w", - "serve": "node browser-sync-config.js", + "serve": "wsrv -o -s -l -p 3000 -a 0.0.0.0 -x ./server/versions.js", "typings": "typings install", - "tslint": "npm run tslint-src && npm run tslint-root", - "tslint-src": "tslint -c tslint.json app/{,**/}**.ts", - "tslint-root": "tslint -c tslint.json *.ts", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'app/{,**/}**.ts'", "licensecheck": "license-check" }, "repository": { @@ -62,47 +63,43 @@ "@angular/router": "3.0.0-alpha.7", "@angular/router-deprecated": "2.0.0-rc.2", "@angular/upgrade": "2.0.0-rc.3", + "alfresco-js-api": "^0.3.0", "systemjs": "0.19.27", "core-js": "2.4.0", "reflect-metadata": "0.1.3", "rxjs": "5.0.0-beta.6", "zone.js": "0.6.12", - "rimraf": "2.5.2", "material-design-icons": "2.2.3", "material-design-lite": "1.1.3", "ng2-translate": "2.2.0", "pdfjs-dist": "1.5.258", "flag-icon-css": "2.3.0", - - "alfresco-js-api": "0.1.0", - "ng2-alfresco-core": "0.2.0", - "ng2-alfresco-datatable": "0.2.0", - "ng2-alfresco-documentlist": "0.2.0", - "ng2-alfresco-login": "0.2.0", - "ng2-alfresco-search": "0.2.0", - "ng2-alfresco-upload": "0.2.0", - "ng2-alfresco-viewer": "0.2.0" + "ng2-alfresco-core": "0.3.0", + "ng2-alfresco-datatable": "0.3.0", + "ng2-alfresco-documentlist": "0.3.0", + "ng2-alfresco-login": "0.3.0", + "ng2-alfresco-search": "0.3.0", + "ng2-alfresco-upload": "0.3.0", + "ng2-alfresco-viewer": "0.3.0", + "ng2-activiti-form": "0.3.0", + "ng2-activiti-tasklist": "0.3.0", + "ng2-activiti-processlist": "0.3.0", + "ng2-alfresco-webscript": "0.3.0" }, "devDependencies": { - "browser-sync": "2.10.0", "concurrently": "2.0.0", - "connect-history-api-fallback": "1.2.0", "license-check": "1.1.5", + "mime": "^1.3.4", "tslint": "3.8.1", "typescript": "1.8.10", - "typings": "1.0.4" + "typings": "1.0.4", + "wsrv": "0.1.4" }, "license-check-config": { "src": [ - "**/*.js", - "**/*.ts", - "!/**/coverage/**/*", - "!/**/demo/**/*", - "!/**/node_modules/**/*", - "!/**/typings/**/*", - "!*.js", - "!app/js/xml2json.js" + "./app/**/*.js", + "!./app/js/xml2json.js" ], "path": "assets/license_header.txt", "blocking": true, diff --git a/demo-shell-ng2/server/versions.js b/demo-shell-ng2/server/versions.js new file mode 100644 index 0000000000..020843cf53 --- /dev/null +++ b/demo-shell-ng2/server/versions.js @@ -0,0 +1,42 @@ +'use strict'; + +// wsrv extension that provides dynamic '/versions' route + +exports.register = function (server, options, next) { + + var packages = [ + 'ng2-activiti-form', + 'ng2-alfresco-core', + 'ng2-alfresco-datatable', + 'ng2-alfresco-documentlist', + 'ng2-alfresco-login', + 'ng2-alfresco-search', + 'ng2-alfresco-upload', + 'ng2-alfresco-viewer', + 'ng2-alfresco-webscript' + ]; + + server.route({ + method: 'GET', + path: '/versions', + handler: function (request, reply) { + var result = { + packages: packages.map(function (packageName) { + return { + name: packageName, + version: require('./../node_modules/' + packageName + '/package.json').version + } + }) + }; + + return reply(result).type('application/json'); + } + }); + + next(); +}; + +exports.register.attributes = { + name: 'ng2-module-versions', + version: '1.0.0' +}; diff --git a/demo-shell-ng2/systemjs.config.js b/demo-shell-ng2/systemjs.config.js index 2e9336eec1..782acb4d85 100644 --- a/demo-shell-ng2/systemjs.config.js +++ b/demo-shell-ng2/systemjs.config.js @@ -17,7 +17,11 @@ 'ng2-alfresco-login': 'node_modules/ng2-alfresco-login/dist', 'ng2-alfresco-search': 'node_modules/ng2-alfresco-search/dist', 'ng2-alfresco-upload': 'node_modules/ng2-alfresco-upload/dist', - 'ng2-alfresco-viewer': 'node_modules/ng2-alfresco-viewer/dist' + 'ng2-activiti-form': 'node_modules/ng2-activiti-form/dist', + 'ng2-alfresco-viewer': 'node_modules/ng2-alfresco-viewer/dist', + 'ng2-alfresco-webscript': 'node_modules/ng2-alfresco-webscript/dist', + 'ng2-activiti-processlist': 'node_modules/ng2-activiti-processlist/dist', + 'ng2-activiti-tasklist': 'node_modules/ng2-activiti-tasklist/dist' }; // packages tells the System loader how to load when no filename and/or no extension var packages = { @@ -33,7 +37,11 @@ 'ng2-alfresco-login': { main: 'index.js', defaultExtension: 'js'}, 'ng2-alfresco-search': { main: 'index.js', defaultExtension: 'js'}, 'ng2-alfresco-upload': { main: 'index.js', defaultExtension: 'js'}, - 'ng2-alfresco-viewer': { main: 'index.js', defaultExtension: 'js'} + 'ng2-alfresco-viewer': { main: 'index.js', defaultExtension: 'js'}, + 'ng2-activiti-form': { main: 'index.js', defaultExtension: 'js'}, + 'ng2-activiti-processlist': { main: 'index.js', defaultExtension: 'js'}, + 'ng2-activiti-tasklist': { main: 'index.js', defaultExtension: 'js'}, + 'ng2-alfresco-webscript': { main: 'index.js', defaultExtension: 'js'} }; var ngPackageNames = [ 'common', @@ -56,8 +64,13 @@ } // Most environments should use UMD; some (Karma) need the individual index files var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; + // Add package entries for angular packages ngPackageNames.forEach(setPackageConfig); + + // No umd for router yet + packages['@angular/router'] = { main: 'index.js', defaultExtension: 'js' }; + var config = { map: map, packages: packages diff --git a/demo-shell-ng2/tslint.json b/demo-shell-ng2/tslint.json index 8c48e76469..e550ac11d4 100644 --- a/demo-shell-ng2/tslint.json +++ b/demo-shell-ng2/tslint.json @@ -26,7 +26,7 @@ "label-undefined": true, "max-line-length": [ true, - 140 + 180 ], "member-ordering": [ true, diff --git a/demo-shell-ng2/wsrv-config.json b/demo-shell-ng2/wsrv-config.json new file mode 100644 index 0000000000..69fa7707ea --- /dev/null +++ b/demo-shell-ng2/wsrv-config.json @@ -0,0 +1,13 @@ +{ + "watch": [ + "node_modules/ng2-alfresco-core/dist/**/*.{html,css,js}", + "node_modules/ng2-alfresco-datatable/dist/**/*.{html,css,js}", + "node_modules/ng2-alfresco-documentlist/dist/**/*.{html,css,js}", + "node_modules/ng2-alfresco-login/dist/**/*.{html,css,js}", + "node_modules/ng2-alfresco-search/dist/**/*.{html,css,js}", + "node_modules/ng2-alfresco-upload/dist/**/*.{html,css,js}", + "node_modules/ng2-alfresco-viewer/dist/**/*.{html,css,js}", + "node_modules/ng2-alfresco-webscript/dist/**/*.{html,css,js}", + "node_modules/ng2-activiti-form/dist/**/*.{html,css,js}" + ] +} diff --git a/ng2-components/README.md b/ng2-components/README.md index 1160b8df97..6189597f42 100644 --- a/ng2-components/README.md +++ b/ng2-components/README.md @@ -1,12 +1,16 @@ # Alfresco Angular 2 Components

    - + travis
     Status - - Coverage Status + + travis
+    Status + + + Coverage Status license @@ -25,12 +29,25 @@

    +## Base components + - [Core library](ng2-alfresco-core/README.md) + * Material Design elements + * Context Menu component + +## ECM components + - [DataTable](ng2-alfresco-datatable/README.md) -- [Document List](ng2-alfresco-documentlist/README.md) +- [DocumentList](ng2-alfresco-documentlist/README.md) - [Viewer](ng2-alfresco-viewer/README.md) - [Login](ng2-alfresco-login/README.md) - [Upload](ng2-alfresco-upload/README.md) +- [Webscript Viewer](ng2-components/ng2-alfresco-webscript/README.md) + +## BPM components + +- [ProcessList](ng2-activiti-processlist/README.md) +- [Form](ng2-activiti-form/README.md) You can browse all the components at the following address: diff --git a/ng2-components/ng2-activiti-form/.editorconfig b/ng2-components/ng2-activiti-form/.editorconfig new file mode 100644 index 0000000000..75a2477db7 --- /dev/null +++ b/ng2-components/ng2-activiti-form/.editorconfig @@ -0,0 +1,23 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[package.json] +indent_style = space +indent_size = 2 + +[karma.conf.js] +indent_style = space +indent_size = 2 + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/ng2-components/ng2-activiti-form/.gitignore b/ng2-components/ng2-activiti-form/.gitignore new file mode 100644 index 0000000000..787032431d --- /dev/null +++ b/ng2-components/ng2-activiti-form/.gitignore @@ -0,0 +1,11 @@ +npm-debug.log +node_modules/ +.idea/ +typings +coverage/ +dist/ +src/**/*.js +src/**/*.js.map +index.js +index.js.map +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-form/.npmignore b/ng2-components/ng2-activiti-form/.npmignore new file mode 100644 index 0000000000..16ba3e61e5 --- /dev/null +++ b/ng2-components/ng2-activiti-form/.npmignore @@ -0,0 +1,16 @@ +npm-debug.log +.idea + +assets/ +coverage/ +node_modules +typings/ +fonts/ + +/.editorconfig +/.travis.yml +/*.js +/*.json +/*.ts +/*.js.map +/.npmignore diff --git a/ng2-components/ng2-activiti-form/LICENSE b/ng2-components/ng2-activiti-form/LICENSE new file mode 100644 index 0000000000..430d42bbea --- /dev/null +++ b/ng2-components/ng2-activiti-form/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/ng2-components/ng2-activiti-form/README.md b/ng2-components/ng2-activiti-form/README.md new file mode 100644 index 0000000000..e0ddb5af56 --- /dev/null +++ b/ng2-components/ng2-activiti-form/README.md @@ -0,0 +1,283 @@ +# Alfresco Bpm Form component for Angular 2 + +## Prerequisites + +Before you start using this development framework, make sure you have installed all required software and done all the +necessary configuration, see this [page](https://github.com/Alfresco/alfresco-ng2-components/blob/master/PREREQUISITES.md). + +## Install + +```sh +npm install --save ng2-activiti-form +``` + +### Dependencies + +Add the following dependency to your index.html: + +```html + +``` + +You must separately install the following libraries for your application: + +- [ng2-translate](https://github.com/ocombe/ng2-translate) +- [ng2-alfresco-core](https://www.npmjs.com/package/ng2-alfresco-core) + +```sh +npm install --save ng2-translate ng2-alfresco-core +``` + +#### Material Design Lite + +The style of this component is based on [material design](https://getmdl.io/), so if you want to visualize it correctly you have to add the material +design dependency to your project: + +```sh +npm install --save material-design-icons material-design-lite +``` + +Also make sure you include these dependencies in your `index.html` file: + +```html + + + + +``` + +## Basic usage examples + +### Display form instance by task id + +```html + + +``` + +For an existing Task both form and values will be fetched and displayed. + +### Display form definition by form id + +```html + + +``` + +Only form definition will be fetched + +### Display form definition by form name + +```html + + +``` + +### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form. If there are no form +definied in activiti for the type of the node, a new form will be automaticaly created in activiti. + +```html + + +``` + +### Display form definition by form name, and store the form field as metadata. The param nameNode is optional. + +```html + + +``` + +### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form, and store the form field + as metadata. The param nameNode is optional. + +```html + + +``` + +## Configuration + +### Properties + +The recommended set of properties can be found in the following table: + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| taskId | string | | Task id to fetch corresponding form and values. | +| formId | string | | The id of the form definition to load and display with custom values. | +| formName | string | | Name of hte form definition to load and display with custom values. | +| data | `FormValues` | | Custom form values map to be used with the rendered form. | +| showTitle | boolean | true | Toggle rendering of the form title. | +| showCompleteButton | boolean | true | Toggle rendering of the `Complete` outcome button. | +| showSaveButton | boolean | true | Toggle rendering of the `Save` outcome button. | +| readOnly | boolean | false | Toggle readonly state of the form. Enforces all form widgets render readonly if enabled. | +| showRefreshButton | boolean | true | Toggle rendering of the `Refresh` button. | +| saveMetadata | boolean | false | Store the value of the form as metadata. | +| path | string | | Path of the folder where to store the metadata. | +| nameNode (optional) | string | true | Name to assign to the new node where the metadata are stored. | + + + * {path} string - path of the folder where the to store the metadata + * + * {nameNode} string (optional) - name of the node stored, if not defined the node will be sotred with an uuid as name +#### Advanced properties + + The following properties are for complex customisation purposes: + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| form | `FormModel` | | Underlying form model instance. | +| debugMode | boolean | false | Toggle debug mode, allows displaying additional data for development and debugging purposes. | + +### Events + +| Name | Description | +| --- | --- | +| formLoaded | Invoked when form is loaded or reloaded. | +| formSaved | Invoked when form is submitted with `Save` or custom outcomes. | +| formCompleted | Invoked when form is submitted with `Complete` outcome. | +| executeOutcome | Invoked when any outcome is executed, default behaviour can be prevented via `event.preventDefault()` | + +All `form*` events receive an instance of the `FormModel` as event argument for ease of development: + +**MyView.component.html** +```html + + +``` + +**MyView.component.ts** +```ts +onFormSaved(form: FormModel) { + console.log(form); +} +``` + +#### Controlling outcome execution behaviour + +If absolutely needed it is possible taking full control over form outcome execution by means of `executeOutcome` event. +This event is fired upon each outcome execution, both system and custom ones. + +You can prevent default behaviour by calling `event.preventDefault()`. +This allows for example having custom form validation scenarios and/or additional validation summary presentation. + +Alternatively you may want just running additional code on outcome execution without suppressing default one. + +**MyView.component.html** +```html + + +``` + +**MyView.component.ts** +```ts +import { FormOutcomeEvent } from 'ng2-activiti-form'; + +export class MyView { + + validateForm(event: FormOutcomeEvent) { + let outcome = event.outcome; + + // you can also get additional properties of outcomes + // if you defined them within outcome definition + + if (outcome) { + let form = outcome.form; + if (form) { + // check/update the form here + event.preventDefault(); + } + } + } + +} +``` + +There are two additional functions that can be of a great value when controlling outcomes: + +- `saveTaskForm()` - saves current form +- `completeTaskForm(outcome?: string)` - save and complete form with a given outcome name + +**Please note that if `event.preventDefault()` is not called then default outcome behaviour +will also be executed after your custom code.** + +## Supported form widgets + +- [x] Tabs +- [x] Text +- [x] Multiline Text +- [x] Number +- [x] Checkbox +- [ ] Date +- Dropdown + * [x] Manual + * [x] REST service + * [ ] Data source +- [ ] Typeahead +- [ ] Amount +- [x] Radio buttons +- [ ] People +- [ ] Group of People +- [ ] Dynamic Table +- [x] Hyperlink +- Header + * [x] Plain header + * [x] Collapsible header +- [ ] Attach +- [x] Display value +- [x] Display text + +## Build from sources + +Alternatively you can build component from sources with the following commands: + +```sh +npm install +npm run build +``` + +### Build the files and keep watching for changes + +```sh +$ npm run build:w +``` + +### Running unit tests + +```sh +npm test +``` + +### Running unit tests in browser + +```sh +npm test-browser +``` + +This task rebuilds all the code, runs tslint, license checks and other quality check tools +before performing unit testing. + +### Code coverage + +```sh +npm run coverage +``` diff --git a/ng2-components/ng2-activiti-form/assets/license_header.txt b/ng2-components/ng2-activiti-form/assets/license_header.txt new file mode 100644 index 0000000000..83fd1531a3 --- /dev/null +++ b/ng2-components/ng2-activiti-form/assets/license_header.txt @@ -0,0 +1,16 @@ +/*! + * @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. + */ \ No newline at end of file diff --git a/ng2-components/ng2-activiti-form/index.ts b/ng2-components/ng2-activiti-form/index.ts new file mode 100644 index 0000000000..093ae1f90c --- /dev/null +++ b/ng2-components/ng2-activiti-form/index.ts @@ -0,0 +1,33 @@ +/*! + * @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 { FormService } from './src/services/form.service'; +import { EcmModelService } from './src/services/ecm-model.service'; +import { NodeService } from './src/services/node.service'; + +export * from './src/components/activiti-form.component'; +export * from './src/services/form.service'; +export * from './src/components/widgets/index'; +export * from './src/services/ecm-model.service'; +export * from './src/services/node.service'; + + +export const ATIVITI_FORM_PROVIDERS: [any] = [ + FormService, + EcmModelService, + NodeService +]; diff --git a/ng2-components/ng2-activiti-form/karma-test-shim.js b/ng2-components/ng2-activiti-form/karma-test-shim.js new file mode 100644 index 0000000000..2d378ec947 --- /dev/null +++ b/ng2-components/ng2-activiti-form/karma-test-shim.js @@ -0,0 +1,107 @@ +// Tun on full stack traces in errors to help debugging +Error.stackTraceLimit = Infinity; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +__karma__.loaded = function() {}; + +var map = { + 'app': 'base/dist', + 'rxjs': 'base/node_modules/rxjs', + '@angular': 'base/node_modules/@angular', + 'ng2-alfresco-core': '/base/node_modules/ng2-alfresco-core/dist', + 'ng2-translate' : '/base/node_modules/ng2-translate' +}; + +var packages = { + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-translate': { defaultExtension: 'js' } +}; + +var packageNames = [ + '@angular/common', + '@angular/compiler', + '@angular/core', + '@angular/http', + '@angular/platform-browser', + '@angular/platform-browser-dynamic', + '@angular/router', + '@angular/router-deprecated', + '@angular/testing', + '@angular/upgrade' +]; + +packageNames.forEach(function(pkgName) { + packages[pkgName] = { main: 'index.js', defaultExtension: 'js' }; +}); + +packages['base/dist'] = { + defaultExtension: 'js', + format: 'register', + map: Object.keys(window.__karma__.files).filter(onlyAppFiles).reduce(createPathRecords, {}) +}; + +var config = { + map: map, + packages: packages +}; + +System.config(config); + +System.import('@angular/platform-browser/src/browser/browser_adapter') + .then(function(browser_adapter) { browser_adapter.BrowserDomAdapter.makeCurrent(); }) + .then(function () { + return Promise.all([ + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') + ]) + }) + .then(function (providers) { + var testing = providers[0]; + var testingBrowser = providers[1]; + + testing.setBaseTestProviders( + testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, + testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS); + + }) + .then(function() { return Promise.all(resolveTestFiles()); }) + .then( + function() { + __karma__.start(); + }, + function(error) { + if(typeof __karma__.error == 'function') { + __karma__.error(error.stack || error); + }else{ + console.error(error); + } + } + ); +function createPathRecords(pathsMapping, appPath) { + var pathParts = appPath.split('/'); + var moduleName = './' + pathParts.slice(Math.max(pathParts.length - 2, 1)).join('/'); + moduleName = moduleName.replace(/\.js$/, ''); + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath]; + return pathsMapping; +} + +function onlyAppFiles(filePath) { + return /\/base\/dist\/(?!.*\.spec\.js$).*\.js$/.test(filePath); +} + +function onlySpecFiles(path) { + return /\.spec\.js$/.test(path); +} + +function resolveTestFiles() { + return Object.keys(window.__karma__.files) // All files served by Karma. + .filter(onlySpecFiles) + .map(function(moduleName) { + // loads all spec files via their global module names (e.g. + // 'base/dist/vg-player/vg-player.spec') + return System.import(moduleName); + }); +} diff --git a/ng2-components/ng2-activiti-form/karma.conf.js b/ng2-components/ng2-activiti-form/karma.conf.js new file mode 100644 index 0000000000..40bb7d31ca --- /dev/null +++ b/ng2-components/ng2-activiti-form/karma.conf.js @@ -0,0 +1,99 @@ +'use strict'; + +module.exports = function (config) { + var configuration = { + basePath: '.', + + frameworks: ['jasmine-ajax', 'jasmine'], + + files: [ + // paths loaded by Karma + {pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: true}, + {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: false}, + {pattern: 'node_modules/zone.js/dist/zone.js', included: true, watched: true}, + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.map', included: false, watched: false}, + {pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', included: true, watched: false}, + + {pattern: 'karma-test-shim.js', included: true, watched: true}, + + // paths loaded via module imports + {pattern: 'dist/**/*.js', included: false, watched: true}, + {pattern: 'dist/**/*.html', included: true, served: true, watched: true}, + {pattern: 'dist/**/*.css', included: true, served: true, watched: true}, + + // paths to support debugging with source maps in dev tools + {pattern: 'src/**/*.ts', included: false, watched: false}, + {pattern: 'dist/**/*.js.map', included: false, watched: false} + ], + + exclude: [ + 'node_modules/**/*spec.js' + ], + + // proxied base paths + proxies: { + // required for component assets fetched by Angular's compiler + '/src/': '/base/src/' + }, + + port: 9876, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + colors: true, + + autoWatch: true, + + browsers: ['Chrome'], + + customLaunchers: { + Chrome_travis_ci: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + + // Karma plugins loaded + plugins: [ + 'karma-jasmine', + 'karma-coverage', + 'karma-jasmine-ajax', + 'karma-chrome-launcher', + 'karma-mocha-reporter', + 'karma-jasmine-html-reporter' + ], + + // Coverage reporter generates the coverage + reporters: ['mocha', 'coverage', 'kjhtml'], + + // Source files that you wanna generate coverage for. + // Do not include tests or libraries (these files will be instrumented by Istanbul) + preprocessors: { + 'dist/**/!(*spec).js': ['coverage'] + }, + + coverageReporter: { + dir: 'coverage/', + subdir: 'report', + reporters: [ + {type: 'text'}, + {type: 'json', file: 'coverage-final.json'}, + {type: 'html'}, + {type: 'lcov'} + ] + } + }; + + if (process.env.TRAVIS) { + configuration.browsers = ['Chrome_travis_ci']; + } + + config.set(configuration) +}; diff --git a/ng2-components/ng2-activiti-form/package.json b/ng2-components/ng2-activiti-form/package.json new file mode 100644 index 0000000000..d0594e8c52 --- /dev/null +++ b/ng2-components/ng2-activiti-form/package.json @@ -0,0 +1,106 @@ +{ + "name": "ng2-activiti-form", + "description": "Alfresco Activiti Form Component for Angular 2", + "version": "0.3.0", + "author": "Alfresco Software, Ltd.", + "scripts": { + "postinstall": "typings install", + "clean": "rimraf dist node_modules typings", + "typings": "typings install", + "server": "wsrv -o -p 9875", + "build": "npm run tslint && rimraf dist && tsc && npm run copy-dist && license-check", + "build:w": "npm run tslint && rimraf dist && npm run watch-task", + "watch-task": "concurrently \"npm run tsc:w\" \"npm run copy-dist:w\" \"license-check\"", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'src/{,**/}**.ts'", + "copy-dist": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src", + "copy-dist:w": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src -w", + "tsc": "tsc", + "tsc:w": "tsc -w", + "pretest": "npm run build", + "test": "karma start karma.conf.js --reporters mocha,coverage --single-run", + "test-browser": "concurrently \"karma start karma.conf.js --reporters kjhtml\" \"npm run watch-task\"", + "posttest": "remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html && remap-istanbul -i coverage/report/coverage-final.json -o coverage/report/coverage-final.json", + "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report", + "prepublish": "npm run build", + "travis": "npm link ng2-alfresco-core" + }, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/Alfresco/alfresco-ng2-components.git" + }, + "bugs": { + "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Denys Vuika", + "email": "denys.vuika@gmail.com" + } + ], + "keywords": [ + "ng2", + "angular", + "angular2", + "alfresco", + "activiti" + ], + "dependencies": { + "@angular/common": "2.0.0-rc.3", + "@angular/compiler": "2.0.0-rc.3", + "@angular/core": "2.0.0-rc.3", + "@angular/forms": "0.1.1", + "@angular/http": "2.0.0-rc.3", + "@angular/platform-browser": "2.0.0-rc.3", + "@angular/platform-browser-dynamic": "2.0.0-rc.3", + "@angular/router": "3.0.0-alpha.7", + "@angular/router-deprecated": "2.0.0-rc.2", + "@angular/upgrade": "2.0.0-rc.3", + "alfresco-js-api": "^0.3.0", + "systemjs": "0.19.27", + "core-js": "2.4.0", + "reflect-metadata": "0.1.3", + "rxjs": "5.0.0-beta.6", + "zone.js": "0.6.12", + "ng2-translate": "2.2.2", + "ng2-alfresco-core": "0.3.0" + }, + "peerDependencies": { + "material-design-icons": "^2.2.3", + "material-design-lite": "^1.1.3" + }, + "devDependencies": { + "concurrently": "2.1.0", + "coveralls": "2.11.9", + "cpx": "1.3.1", + "jasmine-core": "2.4.1", + "karma": "0.13.22", + "karma-chrome-launcher": "1.0.1", + "karma-coverage": "1.0.0", + "karma-coveralls": "1.1.2", + "karma-jasmine": "1.0.2", + "karma-jasmine-ajax": "0.1.13", + "karma-jasmine-html-reporter": "0.2.0", + "karma-jasmine-ajax": "0.1.13", + "karma-mocha-reporter": "2.0.3", + "license-check": "1.1.5", + "remap-istanbul": "0.6.3", + "rimraf": "2.5.2", + "traceur": "0.0.91", + "tslint": "3.8.1", + "typescript": "1.8.10", + "typings": "1.0.4", + "wsrv": "0.1.3" + }, + "license-check-config": { + "src": [ + "./dist/**/*.js" + ], + "path": "assets/license_header.txt", + "blocking": true, + "logInfo": false, + "logError": true + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.css b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.css new file mode 100644 index 0000000000..6eb9bd0de9 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.css @@ -0,0 +1,25 @@ +.activiti-form-container { + width: 100%; + min-height: 100px; +} + +.activiti-form-container > .mdl-card__media { + background-color: #fff; +} + +.activiti-form-debug-container { + padding: 10px; +} + +.activiti-form-debug-container .debug-toggle-text { + padding-left: 15px; + cursor: pointer; +} + +.activiti-form-debug-container .debug-toggle-text:hover { + font-weight: bold; +} + +.activiti-form-hide-button { + display: none; +} diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html new file mode 100644 index 0000000000..7bb2551e8a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html @@ -0,0 +1,56 @@ +
    +
    +

    Please select a Task

    +
    +
    +
    +
    +

    {{form.taskName}}

    +
    +
    +
    + +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + + + +
    +

    Values

    +
    {{form.values | json}}
    + +

    Form

    +
    {{form.json | json}}
    +
    +
    diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts new file mode 100644 index 0000000000..133206d5b6 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts @@ -0,0 +1,637 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { Observable } from 'rxjs/Rx'; +import { SimpleChange } from '@angular/core'; +import { ActivitiForm } from './activiti-form.component'; +import { FormModel, FormOutcomeModel, FormFieldModel, FormOutcomeEvent } from './widgets/index'; +import { FormService } from './../services/form.service'; +import { WidgetVisibilityService } from './../services/widget-visibility.service'; +import { ContainerWidget } from './widgets/container/container.widget'; + +describe('ActivitiForm', () => { + + let componentHandler: any; + let formService: FormService; + let formComponent: ActivitiForm; + let visibilityService: WidgetVisibilityService; + + beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + visibilityService = jasmine.createSpyObj('WidgetVisibilityService', [ + 'updateVisibilityForForm', 'getTaskProcessVariableModelsForTask' + ]); + window['componentHandler'] = componentHandler; + + formService = new FormService(null, null); + formComponent = new ActivitiForm(formService, visibilityService, null, null, null); + }); + + it('should upgrade MDL content on view checked', () => { + formComponent.ngAfterViewChecked(); + expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled(); + }); + + it('should setup MDL content only if component handler available', () => { + expect(formComponent.setupMaterialComponents()).toBeTruthy(); + + window['componentHandler'] = null; + expect(formComponent.setupMaterialComponents()).toBeFalsy(); + }); + + it('should start loading form on init', () => { + spyOn(formComponent, 'loadForm').and.stub(); + formComponent.ngOnInit(); + expect(formComponent.loadForm).toHaveBeenCalled(); + }); + + it('should check form', () => { + expect(formComponent.hasForm()).toBeFalsy(); + formComponent.form = new FormModel(); + expect(formComponent.hasForm()).toBeTruthy(); + }); + + it('should allow title if task name available', () => { + let formModel = new FormModel(); + formComponent.form = formModel; + + expect(formComponent.showTitle).toBeTruthy(); + expect(formModel.taskName).toBe(FormModel.UNSET_TASK_NAME); + expect(formComponent.isTitleEnabled()).toBeTruthy(); + + // override property as it's the readonly one + Object.defineProperty(formModel, 'taskName', { + enumerable: false, + configurable: false, + writable: false, + value: null + }); + + expect(formComponent.isTitleEnabled()).toBeFalsy(); + }); + + it('should not allow title', () => { + let formModel = new FormModel(); + + formComponent.form = formModel; + formComponent.showTitle = false; + + expect(formModel.taskName).toBe(FormModel.UNSET_TASK_NAME); + expect(formComponent.isTitleEnabled()).toBeFalsy(); + }); + + it('should not enable outcome button when model missing', () => { + expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy(); + }); + + it('should enable custom outcome buttons', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' }); + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy(); + }); + + + it('should allow controlling [complete] button visibility', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION }); + + formComponent.showSaveButton = true; + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy(); + + formComponent.showSaveButton = false; + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); + }); + + it('should allow controlling [save] button visibility', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION }); + + formComponent.showCompleteButton = true; + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy(); + + formComponent.showCompleteButton = false; + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); + }); + + it('should load form on refresh', () => { + spyOn(formComponent, 'loadForm').and.stub(); + + formComponent.onRefreshClicked(); + expect(formComponent.loadForm).toHaveBeenCalled(); + }); + + it('should get form by task id on load', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + const taskId = '123'; + + formComponent.taskId = taskId; + formComponent.loadForm(); + + expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId); + expect(visibilityService.getTaskProcessVariableModelsForTask).toHaveBeenCalledWith(taskId); + }); + + it('should get form definition by form id on load', () => { + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + const formId = '123'; + + formComponent.formId = formId; + formComponent.loadForm(); + + expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId); + }); + + it('should get form definition by form name on load', () => { + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + const formName = '
    '; + + formComponent.formName = formName; + formComponent.loadForm(); + + expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName); + }); + + it('should reload form by task id on binding changes', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + const taskId = ''; + + let change = new SimpleChange(null, taskId); + formComponent.ngOnChanges({ 'taskId': change }); + + expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId); + }); + + it('should reload form definition by form id on binding changes', () => { + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + const formId = '123'; + + let change = new SimpleChange(null, formId); + formComponent.ngOnChanges({ 'formId': change }); + + expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId); + }); + + it('should reload form definition by name on binding changes', () => { + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + const formName = ''; + + let change = new SimpleChange(null, formName); + formComponent.ngOnChanges({ 'formName': change }); + + expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName); + }); + + it('should not get form on load', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + + formComponent.taskId = null; + formComponent.formId = null; + formComponent.formName = null; + formComponent.loadForm(); + + expect(formComponent.getFormByTaskId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled(); + }); + + it('should not reload form on binding changes', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + + formComponent.ngOnChanges({ 'tag': new SimpleChange(null, 'hello world')}); + + expect(formComponent.getFormByTaskId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled(); + }); + + it('should complete form on custom outcome click', () => { + let formModel = new FormModel(); + let outcomeName = 'Custom Action'; + let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); + + let saved = false; + formComponent.form = formModel; + formComponent.formSaved.subscribe(v => saved = true); + spyOn(formComponent, 'completeTaskForm').and.stub(); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(saved).toBeTruthy(); + expect(formComponent.completeTaskForm).toHaveBeenCalledWith(outcomeName); + }); + + it('should save form on [save] outcome click', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { + id: ActivitiForm.SAVE_OUTCOME_ID, + name: 'Save', + isSystem: true + }); + + formComponent.form = formModel; + spyOn(formComponent, 'saveTaskForm').and.stub(); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(formComponent.saveTaskForm).toHaveBeenCalled(); + }); + + it('should complete form on [complete] outcome click', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { + id: ActivitiForm.COMPLETE_OUTCOME_ID, + name: 'Complete', + isSystem: true + }); + + formComponent.form = formModel; + spyOn(formComponent, 'completeTaskForm').and.stub(); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(formComponent.completeTaskForm).toHaveBeenCalled(); + }); + + it('should emit form saved event on custom outcome click', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom', + isSystem: true + }); + + let saved = false; + formComponent.form = formModel; + formComponent.formSaved.subscribe(v => saved = true); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(saved).toBeTruthy(); + }); + + it('should do nothing when clicking outcome for readonly form', () => { + let formModel = new FormModel(); + const outcomeName = 'Custom Action'; + let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); + + formComponent.form = formModel; + spyOn(formComponent, 'completeTaskForm').and.stub(); + + expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy(); + formComponent.readOnly = true; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + }); + + it('should require outcome model when clicking outcome', () => { + formComponent.form = new FormModel(); + formComponent.readOnly = false; + expect(formComponent.onOutcomeClicked(null)).toBeFalsy(); + }); + + it('should require loaded form when clicking outcome', () => { + let formModel = new FormModel(); + const outcomeName = 'Custom Action'; + let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); + + formComponent.readOnly = false; + formComponent.form = null; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + }); + + it('should not execute unknown system outcome', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { id: 'unknown', name: 'Unknown', isSystem: true }); + + formComponent.form = formModel; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + }); + + it('should require custom action name to complete form', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { id: 'custom' }); + + formComponent.form = formModel; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + + outcome = new FormOutcomeModel(formModel, { id: 'custom', name: 'Custom' }); + spyOn(formComponent, 'completeTaskForm').and.stub(); + expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy(); + }); + + it('should fetch and parse form by task id', () => { + spyOn(formService, 'getTaskForm').and.callFake((taskId) => { + return Observable.create(observer => { + observer.next({ taskId: taskId }); + observer.complete(); + }); + }); + + const taskId = '456'; + let loaded = false; + formComponent.formLoaded.subscribe(() => loaded = true); + + 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', () => { + const error = 'Some error'; + + spyOn(formComponent, 'handleError').and.stub(); + spyOn(formService, 'getTaskForm').and.callFake((taskId) => { + return Observable.throw(error); + }); + + formComponent.getFormByTaskId('123'); + expect(formComponent.handleError).toHaveBeenCalledWith(error); + }); + + it('should apply readonly state when getting form by task id', () => { + spyOn(formService, 'getTaskForm').and.callFake((taskId) => { + return Observable.create(observer => { + observer.next({ taskId: taskId }); + observer.complete(); + }); + }); + + formComponent.readOnly = true; + formComponent.getFormByTaskId('123'); + + expect(formComponent.form).toBeDefined(); + expect(formComponent.form.readOnly).toBe(true); + }); + + it('should fetch and parse form definition by id', () => { + spyOn(formService, 'getFormDefinitionById').and.callFake((formId) => { + return Observable.create(observer => { + observer.next({ id: formId }); + observer.complete(); + }); + }); + + const formId = '456'; + let loaded = false; + formComponent.formLoaded.subscribe(() => loaded = true); + + expect(formComponent.form).toBeUndefined(); + formComponent.getFormDefinitionByFormId(formId); + + expect(loaded).toBeTruthy(); + expect(formService.getFormDefinitionById).toHaveBeenCalledWith(formId); + expect(formComponent.form).toBeDefined(); + expect(formComponent.form.id).toBe(formId); + }); + + it('should handle error when getting form by definition id', () => { + const error = 'Some error'; + + spyOn(formComponent, 'handleError').and.stub(); + spyOn(formService, 'getFormDefinitionById').and.callFake(() => Observable.throw(error)); + + formComponent.getFormDefinitionByFormId('123'); + expect(formService.getFormDefinitionById).toHaveBeenCalledWith('123'); + expect(formComponent.handleError).toHaveBeenCalledWith(error); + }); + + it('should fetch and parse form definition by form name', () => { + spyOn(formService, 'getFormDefinitionByName').and.callFake((formName) => { + return Observable.create(observer => { + observer.next(formName); + observer.complete(); + }); + }); + + spyOn(formService, 'getFormDefinitionById').and.callFake((formName) => { + return Observable.create(observer => { + observer.next({ name: formName }); + observer.complete(); + }); + }); + + const formName = ''; + let loaded = false; + formComponent.formLoaded.subscribe(() => loaded = true); + + expect(formComponent.form).toBeUndefined(); + formComponent.getFormDefinitionByFormName(formName); + + expect(loaded).toBeTruthy(); + expect(formService.getFormDefinitionByName).toHaveBeenCalledWith(formName); + expect(formComponent.form).toBeDefined(); + expect(formComponent.form.name).toBe(formName); + }); + + it('should save task form and raise corresponding event', () => { + spyOn(formService, 'saveTaskForm').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + let saved = false; + let savedForm = null; + formComponent.formSaved.subscribe(form => { + saved = true; + savedForm = form; + }); + + let formModel = new FormModel({ + taskId: '123', + fields: [ + { id: 'field1' }, + { id: 'field2' } + ] + }); + formComponent.form = formModel; + formComponent.saveTaskForm(); + + expect(formService.saveTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values); + expect(saved).toBeTruthy(); + expect(savedForm).toEqual(formModel); + }); + + it('should handle error during form save', () => { + const error = 'Error'; + spyOn(formService, 'saveTaskForm').and.callFake(() => Observable.throw(error)); + spyOn(formComponent, 'handleError').and.stub(); + + formComponent.form = new FormModel({ taskId: '123' }); + formComponent.saveTaskForm(); + + expect(formComponent.handleError).toHaveBeenCalledWith(error); + }); + + it('should require form with task id to save', () => { + spyOn(formService, 'saveTaskForm').and.stub(); + + formComponent.form = null; + formComponent.saveTaskForm(); + + formComponent.form = new FormModel(); + formComponent.saveTaskForm(); + + expect(formService.saveTaskForm).not.toHaveBeenCalled(); + }); + + it('should require form with task id to complete', () => { + spyOn(formService, 'completeTaskForm').and.stub(); + + formComponent.form = null; + formComponent.completeTaskForm('save'); + + formComponent.form = new FormModel(); + formComponent.completeTaskForm('complete'); + + expect(formService.completeTaskForm).not.toHaveBeenCalled(); + }); + + it('should log error to console by default', () => { + const error = 'Error'; + spyOn(console, 'log').and.stub(); + formComponent.handleError(error); + expect(console.log).toHaveBeenCalledWith(error); + }); + + it('should complete form form and raise corresponding event', () => { + spyOn(formService, 'completeTaskForm').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + const outcome = 'complete'; + let completed = false; + formComponent.formCompleted.subscribe(() => completed = true); + + let formModel = new FormModel({ + taskId: '123', + fields: [ + { id: 'field1' }, + { id: 'field2' } + ] + }); + + formComponent.form = formModel; + formComponent.completeTaskForm(outcome); + + expect(formService.completeTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values, outcome); + expect(completed).toBeTruthy(); + }); + + it('should require json to parse form', () => { + expect(formComponent.parseForm(null)).toBeNull(); + }); + + it('should parse form from json', () => { + let form = formComponent.parseForm({ + id: '', + fields: [ + { id: 'field1' } + ] + }); + + expect(form).toBeDefined(); + expect(form.id).toBe(''); + expect(form.fields.length).toBe(1); + expect(form.fields[0].id).toBe('field1'); + }); + + it('should provide outcomes for form definition', () => { + spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough(); + + let form = formComponent.parseForm({ id: '' }); + expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form); + }); + + it('should update the visibility when the container raise the change event', (valueChanged) => { + spyOn(formComponent, 'checkVisibility').and.callThrough(); + let widget = new ContainerWidget(); + let fakeForm = new FormModel(); + let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'}); + widget.formValueChanged.subscribe(field => { valueChanged(); }); + widget.fieldChanged(fakeField); + + expect(formComponent.checkVisibility).toHaveBeenCalledWith(fakeField); + }); + + it('should prevent default outcome execution', () => { + + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom' + }); + + formComponent.form = new FormModel(); + formComponent.executeOutcome.subscribe((event: FormOutcomeEvent) => { + expect(event.outcome).toBe(outcome); + event.preventDefault(); + expect(event.defaultPrevented).toBeTruthy(); + }); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeFalsy(); + }); + + it('should not prevent default outcome execution', () => { + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom' + }); + + formComponent.form = new FormModel(); + formComponent.executeOutcome.subscribe((event: FormOutcomeEvent) => { + expect(event.outcome).toBe(outcome); + expect(event.defaultPrevented).toBeFalsy(); + }); + + spyOn(formComponent, 'completeTaskForm').and.callThrough(); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + + expect(formComponent.completeTaskForm).toHaveBeenCalledWith(outcome.name); + }); + + it('should check visibility only if field with form provided', () => { + + formComponent.checkVisibility(null); + expect(visibilityService.updateVisibilityForForm).not.toHaveBeenCalled(); + + let field = new FormFieldModel(null); + formComponent.checkVisibility(field); + expect(visibilityService.updateVisibilityForForm).not.toHaveBeenCalled(); + + field = new FormFieldModel(new FormModel()); + formComponent.checkVisibility(field); + expect(visibilityService.updateVisibilityForForm).toHaveBeenCalledWith(field.form); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts new file mode 100644 index 0000000000..d763eb0128 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts @@ -0,0 +1,431 @@ +/*! + * @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, + OnInit, AfterViewChecked, OnChanges, + SimpleChanges, + Input, + Output, + EventEmitter +} from '@angular/core'; +import { MATERIAL_DESIGN_DIRECTIVES, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { EcmModelService } from './../services/ecm-model.service'; +import { FormService } from './../services/form.service'; +import { NodeService } from './../services/node.service'; +import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index'; + +import { TabsWidget } from './widgets/tabs/tabs.widget'; +import { ContainerWidget } from './widgets/container/container.widget'; + +declare let __moduleName: string; +declare var componentHandler; + +import { WidgetVisibilityService } from './../services/widget-visibility.service'; + +/** + * @Input + * ActivitiForm can show 4 types of forms searching by 4 type of params: + * 1) Form attached to a task passing the {taskId}. + * + * 2) Form that are only defined with the {formId} (in this case you receive only the form definition and the form will not be + * attached to any process, useful in case you want to use ActivitiForm as form designer), in this case you can pass also other 2 + * parameters: + * - {saveOption} as parameter to tell what is the function to call on the save action. + * - {data} to fill the form field with some data, the id of the form must to match the name of the field of the provided data object. + * + * 3) Form that are only defined with the {formName} (in this case you receive only the form definition and the form will not be + * attached to any process, useful in case you want to use ActivitiForm as form designer), + * in this case you can pass also other 2 parameters: + * - {saveOption} as parameter to tell what is the function to call on the save action. + * - {data} to fill the form field with some data, the id of the form must to match the name of the field of the provided data object. + * + * 4) Form that show the metadata of a {nodeId} + * + * {showTitle} boolean - to hide the title of the form pass false, default true; + * + * {showRefreshButton} boolean - to hide the refresh button of the form pass false, default true; + * + * {showCompleteButton} boolean - to hide the complete button of the form pass false, default true; + * + * {showSaveButton} boolean - to hide the save button of the form pass false, default true; + * + * {saveMetadata} boolean - store the value of the form as metadata, default false; + * + * {path} string - path of the folder where to store the metadata; + * + * {nameNode} string (optional) - Name to assign to the new node where the metadata are stored; + * + * @Output + * {formLoaded} EventEmitter - This event is fired when the form is loaded, it pass all the value in the form. + * {formSaved} EventEmitter - This event is fired when the form is saved, it pass all the value in the form. + * {formCompleted} EventEmitter - This event is fired when the form is completed, it pass all the value in the form. + * + * @returns {ActivitiForm} . + */ +@Component({ + moduleId: __moduleName, + selector: 'activiti-form', + templateUrl: './activiti-form.component.html', + styleUrls: ['./activiti-form.component.css'], + directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget, TabsWidget], + providers: [EcmModelService, FormService, WidgetVisibilityService, NodeService] +}) +export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { + + static SAVE_OUTCOME_ID: string = '$save'; + static COMPLETE_OUTCOME_ID: string = '$complete'; + static CUSTOM_OUTCOME_ID: string = '$custom'; + + @Input() + taskId: string; + + @Input() + nodeId: string; + + @Input() + formId: string; + + @Input() + formName: string; + + @Input() + saveMetadata: boolean = false; + + @Input() + data: FormValues; + + @Input() + path: string; + + @Input() + nameNode: string; + + @Input() + showTitle: boolean = true; + + @Input() + showCompleteButton: boolean = true; + + @Input() + showSaveButton: boolean = true; + + @Input() + readOnly: boolean = false; + + @Input() + showRefreshButton: boolean = true; + + @Output() + formSaved: EventEmitter = new EventEmitter(); + + @Output() + formCompleted: EventEmitter = new EventEmitter(); + + @Output() + formLoaded: EventEmitter = new EventEmitter(); + + @Output() + executeOutcome: EventEmitter = new EventEmitter(); + + form: FormModel; + + debugMode: boolean = false; + + constructor(private formService: FormService, + private visibilityService: WidgetVisibilityService, + private authService: AlfrescoAuthenticationService, + private ecmModelService: EcmModelService, + private nodeService: NodeService) { + } + + hasForm(): boolean { + return this.form ? true : false; + } + + isTitleEnabled(): boolean { + if (this.showTitle) { + if (this.form && this.form.taskName) { + return true; + } + } + return false; + } + + isOutcomeButtonEnabled(outcome: FormOutcomeModel): boolean { + if (outcome && outcome.name) { + if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) { + return this.showCompleteButton; + } + if (outcome.name === FormOutcomeModel.SAVE_ACTION) { + return this.showSaveButton; + } + return true; + } + return false; + } + + ngOnInit() { + if (this.nodeId) { + this.loadActivitiFormForEcmNode(); + } else { + this.loadForm(); + } + } + + ngAfterViewChecked() { + this.setupMaterialComponents(); + } + + ngOnChanges(changes: SimpleChanges) { + let taskId = changes['taskId']; + if (taskId && taskId.currentValue) { + this.getFormByTaskId(taskId.currentValue); + return; + } + + let formId = changes['formId']; + if (formId && formId.currentValue) { + this.getFormDefinitionByFormId(formId.currentValue); + return; + } + + let formName = changes['formName']; + if (formName && formName.currentValue) { + this.getFormDefinitionByFormName(formName.currentValue); + return; + } + } + + /** + * Invoked when user clicks outcome button. + * @param outcome Form outcome model + * @returns {boolean} True if outcome action was executed, otherwise false. + */ + onOutcomeClicked(outcome: FormOutcomeModel): boolean { + if (!this.readOnly && outcome && this.form) { + + let args = new FormOutcomeEvent(outcome); + this.executeOutcome.emit(args); + if (args.defaultPrevented) { + return false; + } + + if (outcome.isSystem) { + if (outcome.id === ActivitiForm.SAVE_OUTCOME_ID) { + this.saveTaskForm(); + return true; + } + + if (outcome.id === ActivitiForm.COMPLETE_OUTCOME_ID) { + this.completeTaskForm(); + return true; + } + + if (outcome.id === ActivitiForm.CUSTOM_OUTCOME_ID) { + this.formSaved.emit(this.form); + this.storeFormAsMetadata(); + return true; + } + } else { + // Note: Activiti is using NAME field rather than ID for outcomes + if (outcome.name) { + this.formSaved.emit(this.form); + this.completeTaskForm(outcome.name); + return true; + } + } + } + + return false; + } + + /** + * Invoked when user clicks form refresh button. + */ + onRefreshClicked() { + this.loadForm(); + } + + loadForm() { + if (this.taskId) { + this.getFormByTaskId(this.taskId); + this.visibilityService.getTaskProcessVariableModelsForTask(this.taskId); + return; + } + + if (this.formId) { + this.getFormDefinitionByFormId(this.formId); + return; + } + + if (this.formName) { + this.getFormDefinitionByFormName(this.formName); + return; + } + } + + setupMaterialComponents(): boolean { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + return true; + } + return false; + } + + getFormByTaskId(taskId: string) { + let data = this.data; + this.formService + .getTaskForm(taskId) + .subscribe( + form => { + this.form = new FormModel(form, data, this.readOnly); + this.formLoaded.emit(this.form); + }, + this.handleError + ); + } + + getFormDefinitionByFormId(formId: string) { + this.formService + .getFormDefinitionById(formId) + .subscribe( + form => { + this.formName = form.name; + this.form = this.parseForm(form); + this.formLoaded.emit(this.form); + }, + this.handleError + ); + } + + getFormDefinitionByFormName(formName: string) { + this.formService + .getFormDefinitionByName(formName) + .subscribe( + id => { + this.formService.getFormDefinitionById(id).subscribe( + form => { + // console.log('Get Form By Form definition Name', form); + this.form = this.parseForm(form); + this.formLoaded.emit(this.form); + }, + this.handleError + ); + }, + this.handleError + ); + } + + saveTaskForm() { + if (this.form && this.form.taskId) { + this.formService + .saveTaskForm(this.form.taskId, this.form.values) + .subscribe( + () => { + this.formSaved.emit(this.form); + this.storeFormAsMetadata(); + }, + this.handleError + ); + } + } + + completeTaskForm(outcome?: string) { + if (this.form && this.form.taskId) { + this.formService + .completeTaskForm(this.form.taskId, this.form.values, outcome) + .subscribe( + () => { + this.formCompleted.emit(this.form); + this.storeFormAsMetadata(); + }, + this.handleError + ); + } + } + + handleError(err: any): any { + console.log(err); + } + + parseForm(json: any): FormModel { + if (json) { + let form = new FormModel(json, this.data, this.readOnly); + if (!json.fields) { + form.outcomes = this.getFormDefinitionOutcomes(form); + } + return form; + } + return null; + } + + /** + * Get custom set of outcomes for a Form Definition. + * @param form Form definition model. + * @returns {FormOutcomeModel[]} Outcomes for a given form definition. + */ + getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] { + return [ + new FormOutcomeModel(form, {id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true}) + ]; + } + + checkVisibility(field: FormFieldModel) { + if (field && field.form) { + this.visibilityService.updateVisibilityForForm(field.form); + } + } + + private loadActivitiFormForEcmNode(): void { + this.nodeService.getNodeMetadata(this.nodeId).subscribe(data => { + this.data = data.metadata; + this.loadFormFromActiviti(data.nodeType); + }, + this.handleError); + } + + public loadFormFromActiviti(nodeType: string): any { + this.formService.searchFrom(nodeType).subscribe( + form => { + if (!form) { + this.formService.createFormFromNodeType(nodeType).subscribe(formMetadata => { + this.loadFormFromFormId(formMetadata.id); + }); + } else { + this.loadFormFromFormId(form.id); + } + }, + this.handleError + ); + } + + private loadFormFromFormId(formId: string) { + this.formId = formId; + this.loadForm(); + } + + private storeFormAsMetadata() { + if (this.saveMetadata) { + this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe(type => { + this.nodeService.createNodeMetadata(type.nodeType || type.entry.prefixedName, EcmModelService.MODEL_NAMESPACE, this.form.values, this.path, this.nameNode); + }, this.handleError + ); + } + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/.gitignore b/ng2-components/ng2-activiti-form/src/components/widgets/.gitignore new file mode 100644 index 0000000000..3a6691d8d4 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/.gitignore @@ -0,0 +1 @@ +/widget.model.ts diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.html new file mode 100644 index 0000000000..67862db94d --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.html @@ -0,0 +1,9 @@ + diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.ts new file mode 100644 index 0000000000..168a6972b3 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/checkbox/checkbox.widget.ts @@ -0,0 +1,31 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'checkbox-widget', + templateUrl: './checkbox.widget.html' +}) +export class CheckboxWidget extends WidgetComponent { + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.css new file mode 100644 index 0000000000..92502b7c0a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.css @@ -0,0 +1,20 @@ +.container-widget {} + +.container-widget__header {} + +.container-widget__header-text { + border-bottom: 1px solid rgba(0,0,0,.87); + margin-left: 10px; + margin-right: 10px; + cursor: default; + + user-select: none; + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE/Edge */ + -webkit-touch-callout: none; /* iOS Safari */ +} + +.container-widget__header-text.collapsible { + cursor: pointer; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html new file mode 100644 index 0000000000..6cffc9730c --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html @@ -0,0 +1,57 @@ +
    +
    +

    + + {{content.name}} +

    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + UNKNOWN WIDGET TYPE: {{field.type}} +
    +
    +
    +
    +
    +
    +
    diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.spec.ts new file mode 100644 index 0000000000..eb721dfc9f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.spec.ts @@ -0,0 +1,112 @@ +/*! + * @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 { it, describe, expect, beforeEach } from '@angular/core/testing'; +import { ContainerWidget } from './container.widget'; +import { FormModel } from './../core/form.model'; +import { ContainerModel } from './../core/container.model'; +import { FormFieldTypes } from './../core/form-field-types'; +import { FormFieldModel } from './../core/form-field.model'; + +describe('ContainerWidget', () => { + + let componentHandler; + + beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + }); + + it('should upgrade MDL content on view init', () => { + let container = new ContainerWidget(); + container.ngAfterViewInit(); + expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled(); + }); + + it('should setup MDL content only if component handler available', () => { + let container = new ContainerWidget(); + expect(container.setupMaterialComponents()).toBeTruthy(); + + window['componentHandler'] = null; + expect(container.setupMaterialComponents()).toBeFalsy(); + }); + + it('should toggle underlying group container', () => { + let container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.GROUP, + params: { + allowCollapse: true + } + }); + + let widget = new ContainerWidget(); + widget.content = container; + + expect(container.isExpanded).toBeTruthy(); + widget.onExpanderClicked(); + expect(container.isExpanded).toBeFalsy(); + widget.onExpanderClicked(); + expect(container.isExpanded).toBeTruthy(); + }); + + it('should toggle only collapsible container', () => { + let container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.GROUP + }); + + let widget = new ContainerWidget(); + widget.content = container; + + expect(container.isExpanded).toBeTruthy(); + widget.onExpanderClicked(); + expect(container.isExpanded).toBeTruthy(); + }); + + it('should toggle only group container', () => { + let container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.CONTAINER, + params: { + allowCollapse: true + } + }); + + let widget = new ContainerWidget(); + widget.content = container; + + expect(container.isExpanded).toBeTruthy(); + widget.onExpanderClicked(); + expect(container.isExpanded).toBeTruthy(); + }); + + it('should send an event when a value is changed in the form', (done) => { + let widget = new ContainerWidget(); + let fakeForm = new FormModel(); + let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'}); + widget.formValueChanged.subscribe(field => { + expect(field).not.toBe(null); + expect(field.id).toBe('fakeField'); + expect(field.value).toBe('fakeValue'); + done(); + }); + + widget.fieldChanged(fakeField); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.ts new file mode 100644 index 0000000000..aa8e0ddab9 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.ts @@ -0,0 +1,69 @@ +/*! + * @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, Input, AfterViewInit, Output, EventEmitter } from '@angular/core'; +import { ContainerModel } from './../core/index'; + +import { MATERIAL_DESIGN_DIRECTIVES } from 'ng2-alfresco-core'; +import { PRIMITIVE_WIDGET_DIRECTIVES } from './../index'; +import { FormFieldModel } from '../core/index'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'container-widget', + templateUrl: './container.widget.html', + styleUrls: ['./container.widget.css'], + directives: [ + MATERIAL_DESIGN_DIRECTIVES, + PRIMITIVE_WIDGET_DIRECTIVES + ] +}) +export class ContainerWidget implements AfterViewInit { + + @Input() + content: ContainerModel; + + @Output() + formValueChanged: EventEmitter = new EventEmitter(); + + onExpanderClicked() { + if (this.content && this.content.isCollapsible()) { + this.content.isExpanded = !this.content.isExpanded; + } + } + + ngAfterViewInit() { + this.setupMaterialComponents(); + } + + setupMaterialComponents(): boolean { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + return true; + } + return false; + } + + fieldChanged(field: FormFieldModel) { + this.formValueChanged.emit(field); + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/container-column.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/container-column.model.spec.ts new file mode 100644 index 0000000000..5296d54288 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/container-column.model.spec.ts @@ -0,0 +1,42 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { ContainerColumnModel } from './container-column.model'; +import { FormFieldModel } from './form-field.model'; + +describe('ContainerColumnModel', () => { + + it('should have max size by default', () => { + let column = new ContainerColumnModel(); + expect(column.size).toBe(12); + }); + + it('should check fields', () => { + let column = new ContainerColumnModel(); + + column.fields = null; + expect(column.hasFields()).toBeFalsy(); + + column.fields = []; + expect(column.hasFields()).toBeFalsy(); + + column.fields = [new FormFieldModel(null, null)]; + expect(column.hasFields()).toBeTruthy(); + }); + +}); diff --git a/ng2-components/ng2-alfresco-documentlist/src/models/column-sorting.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/container-column.model.ts similarity index 65% rename from ng2-components/ng2-alfresco-documentlist/src/models/column-sorting.model.ts rename to ng2-components/ng2-activiti-form/src/components/widgets/core/container-column.model.ts index 6f1f3b6dd5..94216ed655 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/models/column-sorting.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/container-column.model.ts @@ -15,17 +15,14 @@ * limitations under the License. */ -export class ColumnSortingModel { +import { FormFieldModel } from './form-field.model'; - static DEFAULT_DIRECTION: string = 'asc'; +export class ContainerColumnModel { - key: string; - direction: string = ColumnSortingModel.DEFAULT_DIRECTION; + size: number = 12; + fields: FormFieldModel[] = []; - constructor(opts?: any) { - if (opts) { - this.key = opts.key; - this.direction = opts.direction || ColumnSortingModel.DEFAULT_DIRECTION; - } + hasFields(): boolean { + return this.fields && this.fields.length > 0; } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.spec.ts new file mode 100644 index 0000000000..3f007d4869 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.spec.ts @@ -0,0 +1,140 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { ContainerModel } from './container.model'; +import { FormModel } from './form.model'; +import { FormFieldTypes } from './form-field-types'; + +describe('ContainerModel', () => { + + it('should store the form reference', () => { + let form = new FormModel(); + let model = new ContainerModel(form); + expect(model.form).toBe(form); + }); + + it('should store original json', () => { + let json = {}; + let model = new ContainerModel(null, json); + expect(model.json).toBe(json); + }); + + it('should have 1 column layout by default', () => { + let container = new ContainerModel(null, null); + expect(container.numberOfColumns).toBe(1); + }); + + it('should be expanded by default', () => { + let container = new ContainerModel(null, null); + expect(container.isExpanded).toBeTruthy(); + }); + + it('should setup with json config', () => { + let json = { + fieldType: '', + id: '', + name: '', + type: '', + tab: '', + numberOfColumns: 2, + params: {} + }; + let container = new ContainerModel(null, json); + Object.keys(json).forEach(key => { + expect(container[key]).toEqual(json[key]); + }); + }); + + it('should wrap fields into columns on setup', () => { + let form = new FormModel(); + let json = { + fieldType: '', + id: '', + name: '', + type: '', + tab: '', + numberOfColumns: 3, + params: {}, + fields: { + '1': [ + { id: 'field-1' }, + { id: 'field-3' } + ], + '2': [ + { id: 'field-2' } + ], + '3': null + } + }; + let container = new ContainerModel(form, json); + expect(container.columns.length).toBe(3); + + let col1 = container.columns[0]; + expect(col1.fields.length).toBe(2); + expect(col1.fields[0].id).toBe('field-1'); + expect(col1.fields[1].id).toBe('field-3'); + + let col2 = container.columns[1]; + expect(col2.fields.length).toBe(1); + expect(col2.fields[0].id).toBe('field-2'); + + let col3 = container.columns[2]; + expect(col3.fields.length).toBe(0); + }); + + it('should allow collapsing only when of a group type', () => { + let container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.CONTAINER, + params: { + allowCollapse: true + } + }); + + expect(container.isCollapsible()).toBeFalsy(); + container.type = FormFieldTypes.GROUP; + expect(container.isCollapsible()).toBeTruthy(); + }); + + it('should allow collapsing only when explicitly defined in params', () => { + let container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.GROUP, + params: {} + }); + expect(container.isCollapsible()).toBeFalsy(); + + container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.GROUP, + params: { + allowCollapse: true + } + }); + expect(container.isCollapsible()).toBeTruthy(); + }); + + it('should be collapsed by default', () => { + let container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.GROUP, + params: { + allowCollapse: true, + collapseByDefault: true + } + }); + expect(container.isCollapsedByDefault()).toBeTruthy(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.ts new file mode 100644 index 0000000000..dfeaf231b9 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/container.model.ts @@ -0,0 +1,97 @@ +/*! + * @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 { FormWidgetModel } from './form-widget.model'; +import { FormFieldMetadata } from './form-field-metadata'; +import { ContainerColumnModel } from './container-column.model'; +import { FormFieldTypes } from './form-field-types'; +import { FormModel } from './form.model'; +import { FormFieldModel } from './form-field.model'; + +// TODO: inherit FormFieldModel +export class ContainerModel extends FormWidgetModel { + + fieldType: string; + id: string; + name: string; + type: string; + tab: string; + numberOfColumns: number = 1; + params: FormFieldMetadata = {}; + + columns: ContainerColumnModel[] = []; + isExpanded: boolean = true; + + isGroup(): boolean { + return this.type === FormFieldTypes.GROUP; + } + + isCollapsible(): boolean { + let allowCollapse = false; + + if (this.isGroup() && this.params['allowCollapse']) { + allowCollapse = this.params['allowCollapse']; + } + + return allowCollapse; + } + + isCollapsedByDefault(): boolean { + let collapseByDefault = false; + + if (this.isCollapsible() && this.params['collapseByDefault']) { + collapseByDefault = this.params['collapseByDefault']; + } + + return collapseByDefault; + } + + constructor(form: FormModel, json?: any) { + super(form, json); + + if (json) { + this.fieldType = json.fieldType; + this.id = json.id; + this.name = json.name; + this.type = json.type; + this.tab = json.tab; + this.numberOfColumns = json.numberOfColumns; + this.params = json.params || {}; + + let columnSize: number = 12; + if (this.numberOfColumns > 1) { + columnSize = 12 / this.numberOfColumns; + } + + for (let i = 0; i < this.numberOfColumns; i++) { + let col = new ContainerColumnModel(); + col.size = columnSize; + this.columns.push(col); + } + + if (json.fields) { + Object.keys(json.fields).map(key => { + let fields = (json.fields[key] || []).map(f => new FormFieldModel(form, f)); + let col = this.columns[parseInt(key, 10) - 1]; + col.fields = fields; + }); + } + + this.isExpanded = !this.isCollapsedByDefault(); + } + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-metadata.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-metadata.ts new file mode 100644 index 0000000000..02378e3b41 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-metadata.ts @@ -0,0 +1,20 @@ +/*! + * @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 interface FormFieldMetadata { + [key: string]: any; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-option.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-option.ts new file mode 100644 index 0000000000..81b39fc6c1 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-option.ts @@ -0,0 +1,21 @@ +/*! + * @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 interface FormFieldOption { + id: string; + name: string; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts new file mode 100644 index 0000000000..627bcfe5d2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts @@ -0,0 +1,37 @@ +/*! + * @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 FormFieldTypes { + static CONTAINER: string = 'container'; + static GROUP: string = 'group'; + static DROPDOWN: string = 'dropdown'; + static HYPERLINK: string = 'hyperlink'; + static RADIO_BUTTONS: string = 'radio-buttons'; + static DISPLAY_VALUE: string = 'readonly'; + static READONLY_TEXT: string = 'readonly-text'; + static UPLOAD: string = 'upload'; + + static READONLY_TYPES: string[] = [ + FormFieldTypes.HYPERLINK, + FormFieldTypes.DISPLAY_VALUE, + FormFieldTypes.READONLY_TEXT + ]; + + static isReadOnlyType(type: string) { + return FormFieldTypes.READONLY_TYPES.indexOf(type) > -1; + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.spec.ts new file mode 100644 index 0000000000..aa987b5e2a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.spec.ts @@ -0,0 +1,243 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { FormFieldModel } from './form-field.model'; +import { FormFieldTypes } from './form-field-types'; +import { FormModel } from './form.model'; + + +describe('FormFieldModel', () => { + + it('should store the form reference', () => { + let form = new FormModel(); + let model = new FormFieldModel(form); + expect(model.form).toBe(form); + }); + + it('should store original json', () => { + let json = {}; + let model = new FormFieldModel(new FormModel(), json); + expect(model.json).toBe(json); + }); + + it('should setup with json config', () => { + let json = { + fieldType: '', + id: '', + name: '', + type: '', + required: true, + readOnly: true, + overrideId: true, + tab: '', + restUrl: '', + restResponsePath: '', + restIdProperty: '', + restLabelProperty: '', + colspan: 1, + options: [], + hasEmptyValue: true, + className: '', + optionType: '', + params: {}, + hyperlinkUrl: '', + displayText: '', + value: '' + }; + let field = new FormFieldModel(new FormModel(), json); + Object.keys(json).forEach(key => { + expect(field[key]).toBe(json[key]); + }); + }); + + it('should setup empty options collection', () => { + let field = new FormFieldModel(new FormModel(), null); + expect(field.options).toBeDefined(); + expect(field.options.length).toBe(0); + + field = new FormFieldModel(new FormModel(), { options: null }); + expect(field.options).toBeDefined(); + expect(field.options.length).toBe(0); + }); + + it('should setup empty params', () => { + let field = new FormFieldModel(new FormModel(), null); + expect(field.params).toEqual({}); + + field = new FormFieldModel(new FormModel(), { params: null }); + expect(field.params).toEqual({}); + }); + + it('should update form on every value change', () => { + let form = new FormModel(); + let field = new FormFieldModel(form, { id: 'field1' }); + let value = 10; + + spyOn(field, 'updateForm').and.callThrough(); + field.value = value; + + expect(field.value).toBe(value); + expect(field.updateForm).toHaveBeenCalled(); + expect(form.values['field1']).toBe(value); + }); + + it('should get form readonly state', () => { + let form = new FormModel(); + let field = new FormFieldModel(form, null); + + expect(field.readOnly).toBeFalsy(); + form.readOnly = true; + expect(field.readOnly).toBeTruthy(); + }); + + it('should take own readonly state if form is writable', () => { + let form = new FormModel(); + let field = new FormFieldModel(form, { readOnly: true }); + + expect(form.readOnly).toBeFalsy(); + expect(field.readOnly).toBeTruthy(); + }); + + it('should parse and convert empty dropdown value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.DROPDOWN, + value: '' + }); + + expect(field.value).toBe('empty'); + }); + + it('should parse and leave dropdown value as is', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.DROPDOWN, + options: [], + value: 'deferred' + }); + + expect(field.value).toBe('deferred'); + }); + + it('should parse and resolve radio button value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.RADIO_BUTTONS, + options: [ + { id: 'opt1', value: 'Option 1' }, + { id: 'opt2', value: 'Option 2' } + ], + value: 'opt2' + }); + + expect(field.value).toBe('opt2'); + }); + + it('should parse and fall back to first radio button value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.RADIO_BUTTONS, + options: [ + { id: 'opt1', value: 'Option 1' }, + { id: 'opt2', value: 'Option 2' } + ], + value: 'opt3' + }); + + expect(field.value).toBe('opt1'); + }); + + it('should parse and leave radio button value as is', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.RADIO_BUTTONS, + options: [], + value: 'deferred-radio' + }); + expect(field.value).toBe('deferred-radio'); + }); + + it('should update form with empty dropdown value', () => { + let form = new FormModel(); + let field = new FormFieldModel(form, { + id: 'dropdown-1', + type: FormFieldTypes.DROPDOWN + }); + + field.value = 'empty'; + expect(form.values['dropdown-1']).toEqual({}); + + field.value = ''; + expect(form.values['dropdown-1']).toEqual({}); + }); + + it('should update form with dropdown value', () => { + let form = new FormModel(); + let field = new FormFieldModel(form, { + id: 'dropdown-2', + type: FormFieldTypes.DROPDOWN, + options: [ + { id: 'opt1', value: 'Option 1' }, + { id: 'opt2', value: 'Option 2' } + ] + }); + + field.value = 'opt2'; + expect(form.values['dropdown-2']).toEqual(field.options[1]); + }); + + it('should update form with radio button value', () => { + let form = new FormModel(); + let field = new FormFieldModel(form, { + id: 'radio-1', + type: FormFieldTypes.RADIO_BUTTONS, + options: [ + { id: 'opt1', value: 'Option 1' }, + { id: 'opt2', value: 'Option 2' } + ] + }); + + field.value = 'opt2'; + expect(form.values['radio-1']).toEqual(field.options[1]); + }); + + it('should update form with the first radio button value', () => { + let form = new FormModel(); + let field = new FormFieldModel(form, { + id: 'radio-2', + type: FormFieldTypes.RADIO_BUTTONS, + options: [ + { id: 'opt1', value: 'Option 1' }, + { id: 'opt2', value: 'Option 2' } + ] + }); + + field.value = 'missing'; + expect(form.values['radio-2']).toEqual(field.options[0]); + }); + + it('should not update form with display-only field value', () => { + let form = new FormModel(); + + FormFieldTypes.READONLY_TYPES.forEach(typeName => { + let field = new FormFieldModel(form, { + id: typeName, + type: typeName + }); + + field.value = ''; + expect(form.values[field.id]).toBeUndefined(); + }); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts new file mode 100644 index 0000000000..22bd3822ae --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts @@ -0,0 +1,173 @@ +/*! + * @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 { FormWidgetModel } from './form-widget.model'; +import { FormFieldOption } from './form-field-option'; +import { FormFieldTypes } from './form-field-types'; +import { FormFieldMetadata } from './form-field-metadata'; +import { FormModel } from './form.model'; +import { WidgetVisibilityModel } from '../../../models/widget-visibility.model'; + +export class FormFieldModel extends FormWidgetModel { + + private _value: string; + private _readOnly: boolean = false; + + fieldType: string; + id: string; + name: string; + type: string; + required: boolean; + overrideId: boolean; + tab: string; + colspan: number = 1; + options: FormFieldOption[] = []; + restUrl: string; + restResponsePath: string; + restIdProperty: string; + restLabelProperty: string; + hasEmptyValue: boolean; + className: string; + optionType: string; + params: FormFieldMetadata = {}; + hyperlinkUrl: string; + displayText: string; + isVisible: boolean = true; + visibilityCondition: WidgetVisibilityModel = null; + + get value(): any { + return this._value; + } + + set value(v: any) { + this._value = v; + this.updateForm(); + } + + get readOnly(): boolean { + if (this.form && this.form.readOnly) { + return true; + } + return this._readOnly; + } + + constructor(form: FormModel, json?: any) { + super(form, json); + + if (json) { + this.fieldType = json.fieldType; + this.id = json.id; + this.name = json.name; + this.type = json.type; + this.required = json.required; + this._readOnly = json.readOnly; + this.overrideId = json.overrideId; + this.tab = json.tab; + this.restUrl = json.restUrl; + this.restResponsePath = json.restResponsePath; + this.restIdProperty = json.restIdProperty; + this.restLabelProperty = json.restLabelProperty; + this.colspan = json.colspan; + this.options = json.options || []; + this.hasEmptyValue = json.hasEmptyValue; + this.className = json.className; + this.optionType = json.optionType; + this.params = json.params || {}; + this.hyperlinkUrl = json.hyperlinkUrl; + this.displayText = json.displayText; + this.visibilityCondition = json.visibilityCondition; + + this._value = this.parseValue(json); + this.updateForm(); + } + } + + parseValue(json: any): any { + let value = json.value; + + /* + This is needed due to Activiti issue related to reading dropdown values as value string + but saving back as object: { id: , name: } + */ + // TODO: needs review + if (json.type === FormFieldTypes.DROPDOWN) { + if (value === '') { + value = 'empty'; + } + } + + /* + This is needed due to Activiti issue related to reading radio button values as value string + but saving back as object: { id: , name: } + */ + if (json.type === FormFieldTypes.RADIO_BUTTONS) { + // Activiti has a bug with default radio button value, + // so try resolving current one with a fallback to first entry + let entry: FormFieldOption[] = this.options.filter(opt => opt.id === value); + if (entry.length > 0) { + value = entry[0].id; + } else if (this.options.length > 0) { + value = this.options[0].id; + } + } + + return value; + } + + updateForm() { + + switch (this.type) { + case FormFieldTypes.DROPDOWN: + /* + This is needed due to Activiti reading dropdown values as string + but saving back as object: { id: , name: } + */ + if (this.value === 'empty' || this.value === '') { + this.form.values[this.id] = {}; + } else { + let entry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value); + if (entry.length > 0) { + this.form.values[this.id] = entry[0]; + } + } + break; + case FormFieldTypes.RADIO_BUTTONS: + /* + This is needed due to Activiti issue related to reading radio button values as value string + but saving back as object: { id: , name: } + */ + let entry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value); + if (entry.length > 0) { + this.form.values[this.id] = entry[0]; + } else if (this.options.length > 0) { + this.form.values[this.id] = this.options[0]; + } + break; + case FormFieldTypes.UPLOAD: + if (this.value && this.value.length > 0) { + this.form.values[this.id] = `${this.value[0].id}`; + } else { + this.form.values[this.id] = null; + } + break; + default: + if (!FormFieldTypes.isReadOnlyType(this.type)) { + this.form.values[this.id] = this.value; + } + } + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome-event.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome-event.model.ts new file mode 100644 index 0000000000..abb290c072 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome-event.model.ts @@ -0,0 +1,41 @@ +/*! + * @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 { FormOutcomeModel } from './form-outcome.model'; + +export class FormOutcomeEvent { + + private _outcome: FormOutcomeModel; + private _defaultPrevented: boolean = false; + + get outcome(): FormOutcomeModel { + return this._outcome; + } + + get defaultPrevented() { + return this._defaultPrevented; + } + + constructor(outcome: FormOutcomeModel) { + this._outcome = outcome; + } + + preventDefault() { + this._defaultPrevented = true; + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.spec.ts new file mode 100644 index 0000000000..88dd417cc6 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.spec.ts @@ -0,0 +1,46 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { FormModel } from './form.model'; +import { FormOutcomeModel } from './form-outcome.model'; + +describe('FormOutcomeModel', () => { + + it('should setup with json config', () => { + let json = { + id: '', + name: '' + }; + let model = new FormOutcomeModel(null, json); + expect(model.id).toBe(json.id); + expect(model.name).toBe(json.name); + }); + + it('should store the form reference', () => { + let form = new FormModel(); + let model = new FormOutcomeModel(form); + expect(model.form).toBe(form); + }); + + it('should store original json', () => { + let json = {}; + let model = new FormOutcomeModel(null, json); + expect(model.json).toBe(json); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.ts new file mode 100644 index 0000000000..f0eb0abb05 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-outcome.model.ts @@ -0,0 +1,48 @@ +/*! + * @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 { FormWidgetModel } from './form-widget.model'; +import { FormModel } from './form.model'; + +export class FormOutcomeModel extends FormWidgetModel { + + static SAVE_ACTION: string = 'Save'; // Activiti 'Save' action name + static COMPLETE_ACTION: string = 'Complete'; // Activiti 'Complete' action name + + private _id: string; + private _name: string; + + isSystem: boolean = false; + + get id() { + return this._id; + } + + get name() { + return this._name; + } + + constructor(form: FormModel, json?: any) { + super(form, json); + + if (json) { + this._id = json.id; + this._name = json.name; + this.isSystem = json.isSystem ? true : false; + } + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-values.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-values.ts new file mode 100644 index 0000000000..cfd32ee6a4 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-values.ts @@ -0,0 +1,21 @@ +/*! + * @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 { FormFieldMetadata } from './form-field-metadata'; + +export interface FormValues extends FormFieldMetadata { +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts new file mode 100644 index 0000000000..0481b59432 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts @@ -0,0 +1,36 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { FormModel } from './form.model'; +import { FormWidgetModel } from './form-widget.model'; + +describe('FormWidgetModel', () => { + + it('should store the form reference', () => { + let form = new FormModel(); + let model = new FormWidgetModel(form, null); + expect(model.form).toBe(form); + }); + + it('should store original json', () => { + let json = {}; + let model = new FormWidgetModel(null, json); + expect(model.json).toBe(json); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.ts new file mode 100644 index 0000000000..39415c2547 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.ts @@ -0,0 +1,41 @@ +/*! + * @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 { FormModel } from './form.model'; + +export class FormWidgetModel { + + private _form: FormModel; + private _json: any; + + get form(): FormModel { + return this._form; + } + + get json(): any { + return this._json; + } + + constructor(form: FormModel, json: any) { + this._form = form; + this._json = json; + } +} + +export interface FormWidgetModelCache { + [key: string]: T; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts new file mode 100644 index 0000000000..1f5d36304a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts @@ -0,0 +1,295 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { FormModel } from './form.model'; +import { TabModel } from './tab.model'; +import { ContainerModel } from './container.model'; +import { FormOutcomeModel } from './form-outcome.model'; +import { FormValues } from './form-values'; + +describe('FormModel', () => { + + it('should store original json', () => { + let json = {}; + let form = new FormModel(json); + expect(form.json).toBe(json); + }); + + it('should setup properties with json', () => { + let json = { + id: '', + name: '', + taskId: '', + taskName: '' + }; + let form = new FormModel(json); + + Object.keys(json).forEach(key => { + expect(form[key]).toEqual(form[key]); + }); + }); + + it('should take form name when task name is missing', () => { + let json = { + id: '', + name: '' + }; + let form = new FormModel(json); + expect(form.taskName).toBe(json.name); + }); + + it('should use fallback value for task name', () => { + let form = new FormModel({}); + expect(form.taskName).toBe(FormModel.UNSET_TASK_NAME); + }); + + it('should set readonly state from params', () => { + let form = new FormModel({}, null, true); + expect(form.readOnly).toBeTruthy(); + }); + + it('should check tabs', () => { + let form = new FormModel(); + + form.tabs = null; + expect(form.hasTabs()).toBeFalsy(); + + form.tabs = []; + expect(form.hasTabs()).toBeFalsy(); + + form.tabs = [new TabModel(null)]; + expect(form.hasTabs()).toBeTruthy(); + }); + + it('should check fields', () => { + let form = new FormModel(); + + form.fields = null; + expect(form.hasFields()).toBeFalsy(); + + form.fields = []; + expect(form.hasFields()).toBeFalsy(); + + form.fields = [new ContainerModel(null)]; + expect(form.hasFields()).toBeTruthy(); + }); + + it('should check outcomes', () => { + let form = new FormModel(); + + form.outcomes = null; + expect(form.hasOutcomes()).toBeFalsy(); + + form.outcomes = []; + expect(form.hasOutcomes()).toBeFalsy(); + + form.outcomes = [new FormOutcomeModel(null)]; + expect(form.hasOutcomes()).toBeTruthy(); + }); + + it('should parse tabs', () => { + let json = { + tabs: [ + { id: 'tab1' }, + { id: 'tab2' } + ] + }; + + let form = new FormModel(json); + expect(form.tabs.length).toBe(2); + expect(form.tabs[0].id).toBe('tab1'); + expect(form.tabs[1].id).toBe('tab2'); + }); + + it('should parse fields', () => { + let json = { + fields: [ + { id: 'field1' }, + { id: 'field2' } + ] + }; + + let form = new FormModel(json); + expect(form.fields.length).toBe(2); + expect(form.fields[0].id).toBe('field1'); + expect(form.fields[1].id).toBe('field2'); + }); + + it('should parse fields from the definition', () => { + let json = { + fields: null, + formDefinition: { + fields: [ + { id: 'field1' }, + { id: 'field2' } + ] + } + }; + + let form = new FormModel(json); + expect(form.fields.length).toBe(2); + expect(form.fields[0].id).toBe('field1'); + expect(form.fields[1].id).toBe('field2'); + }); + + it('should convert missing fields to empty collection', () => { + let json = { + fields: null + }; + + let form = new FormModel(json); + expect(form.fields).toBeDefined(); + expect(form.fields.length).toBe(0); + }); + + it('should put fields into corresponding tabs', () => { + let json = { + tabs: [ + { id: 'tab1' }, + { id: 'tab2' } + ], + fields: [ + { id: 'field1', tab: 'tab1' }, + { id: 'field2', tab: 'tab2' }, + { id: 'field3', tab: 'tab1' }, + { id: 'field4', tab: 'missing-tab' } + ] + }; + + let form = new FormModel(json); + expect(form.tabs.length).toBe(2); + expect(form.fields.length).toBe(4); + + let tab1 = form.tabs[0]; + expect(tab1.fields.length).toBe(2); + expect(tab1.fields[0].id).toBe('field1'); + expect(tab1.fields[1].id).toBe('field3'); + + let tab2 = form.tabs[1]; + expect(tab2.fields.length).toBe(1); + expect(tab2.fields[0].id).toBe('field2'); + }); + + it('should apply external data', () => { + let data: FormValues = { + field1: 'one', + field2: 'two' + }; + + let json = { + fields: [ + { + fieldType: 'ContainerRepresentation', + id: 'container1', + type: 'container', + numberOfColumns: 2, + fields: { + '1': [ + { + fieldType: 'FormFieldRepresentation', + type: 'text', + id: 'field1' + } + ], + '2': [ + { + fieldType: 'FormFieldRepresentation', + type: 'text', + id: 'field2' + }, + { + fieldType: 'FormFieldRepresentation', + type: 'text', + id: 'field3', + value: 'original-value' + } + ] + } + } + ] + }; + + let form = new FormModel(json, data); + expect(form.fields.length).toBe(1); + + let container = form.fields[0]; + expect(container.columns.length).toBe(2); + + let column1 = container.columns[0]; + let column2 = container.columns[1]; + expect(column1.fields.length).toBe(1); + expect(column2.fields.length).toBe(2); + + let field1 = column1.fields[0]; + expect(field1.id).toBe('field1'); + expect(field1.value).toBe('one'); + + let field2 = column2.fields[0]; + expect(field2.id).toBe('field2'); + expect(field2.value).toBe('two'); + + let field3 = column2.fields[1]; + expect(field3.id).toBe('field3'); + expect(field3.value).toBe('original-value'); + }); + + it('should create standard form outcomes', () => { + let json = { + fields: [ + { id: 'container1' } + ] + }; + + let form = new FormModel(json); + expect(form.outcomes.length).toBe(2); + + expect(form.outcomes[0].id).toBe(FormModel.SAVE_OUTCOME); + expect(form.outcomes[0].isSystem).toBeTruthy(); + + expect(form.outcomes[1].id).toBe(FormModel.COMPLETE_OUTCOME); + expect(form.outcomes[1].isSystem).toBeTruthy(); + }); + + it('should create outcomes only when fields available', () => { + let json = { + fields: null + }; + let form = new FormModel(json); + expect(form.outcomes.length).toBe(0); + }); + + it('should use custom form outcomes', () => { + let json = { + fields: [ + { id: 'container1' } + ], + outcomes: [ + { id: 'custom-1', name: 'custom 1' } + ] + }; + + let form = new FormModel(json); + expect(form.outcomes.length).toBe(2); + + expect(form.outcomes[0].id).toBe(FormModel.SAVE_OUTCOME); + expect(form.outcomes[0].isSystem).toBeTruthy(); + + expect(form.outcomes[1].id).toBe('custom-1'); + expect(form.outcomes[1].isSystem).toBeFalsy(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts new file mode 100644 index 0000000000..00f72226f5 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts @@ -0,0 +1,151 @@ +/*! + * @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 { FormWidgetModelCache } from './form-widget.model'; +import { FormValues } from './form-values'; +import { ContainerModel } from './container.model'; +import { TabModel } from './tab.model'; +import { FormOutcomeModel } from './form-outcome.model'; + +export class FormModel { + + static UNSET_TASK_NAME: string = 'Nameless task'; + static SAVE_OUTCOME: string = '$save'; + static COMPLETE_OUTCOME: string = '$complete'; + + private _id: string; + private _name: string; + private _taskId: string; + private _taskName: string = FormModel.UNSET_TASK_NAME; + + get id(): string { + return this._id; + } + + get name(): string { + return this._name; + } + + get taskId(): string { + return this._taskId; + } + + get taskName(): string { + return this._taskName; + } + + readOnly: boolean = false; + tabs: TabModel[] = []; + fields: ContainerModel[] = []; + outcomes: FormOutcomeModel[] = []; + + values: FormValues = {}; + + private _json: any; + + get json() { + return this._json; + } + + hasTabs(): boolean { + return this.tabs && this.tabs.length > 0; + } + + hasFields(): boolean { + return this.fields && this.fields.length > 0; + } + + hasOutcomes(): boolean { + return this.outcomes && this.outcomes.length > 0; + } + + constructor(json?: any, data?: FormValues, readOnly: boolean = false) { + this.readOnly = readOnly; + if (json) { + this._json = json; + + this._id = json.id; + this._name = json.name; + this._taskId = json.taskId; + this._taskName = json.taskName || json.name || FormModel.UNSET_TASK_NAME; + + let tabCache: FormWidgetModelCache = {}; + + this.tabs = (json.tabs || []).map(t => { + let model = new TabModel(this, t); + tabCache[model.id] = model; + return model; + }); + + this.fields = this.parseContainerFields(json); + + if (data) { + this.loadData(data); + } + + for (let i = 0; i < this.fields.length; i++) { + let field = this.fields[i]; + if (field.tab) { + let tab = tabCache[field.tab]; + if (tab) { + tab.fields.push(new ContainerModel(this, field.json)); + } + } + } + if (json.fields) { + let saveOutcome = new FormOutcomeModel(this, { id: FormModel.SAVE_OUTCOME, name: 'Save', isSystem: true }); + let completeOutcome = new FormOutcomeModel(this, {id: FormModel.COMPLETE_OUTCOME, name: 'Complete', isSystem: true }); + + let customOutcomes = (json.outcomes || []).map(obj => new FormOutcomeModel(this, obj)); + + this.outcomes = [saveOutcome].concat( + customOutcomes.length > 0 ? customOutcomes : [completeOutcome] + ); + } + } + } + + private parseContainerFields(json: any): ContainerModel[] { + let fields = []; + + if (json.fields) { + fields = json.fields; + } else if (json.formDefinition && json.formDefinition.fields) { + fields = json.formDefinition.fields; + } + + return fields.map(obj => new ContainerModel(this, obj)); + } + + // Loads external data and overrides field values + // Typically used when form definition and form data coming from different sources + private loadData(data: FormValues) { + for (let i = 0; i < this.fields.length; i++) { + let container = this.fields[i]; + for (let i = 0; i < container.columns.length; i++) { + let column = container.columns[i]; + for (let i = 0; i < column.fields.length; i++) { + let field = column.fields[i]; + if (data[field.id]) { + field.json.value = data[field.id]; + field.value = data[field.id]; + } + } + } + } + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts new file mode 100644 index 0000000000..7a28d27c75 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts @@ -0,0 +1,29 @@ +/*! + * @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 * from './form-field-metadata'; +export * from './form-values'; +export * from './form-field-types'; +export * from './form-field-option'; +export * from './form-widget.model'; +export * from './form-field.model'; +export * from './form.model'; +export * from './container-column.model'; +export * from './container.model'; +export * from './tab.model'; +export * from './form-outcome.model'; +export * from './form-outcome-event.model'; diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.spec.ts new file mode 100644 index 0000000000..6d060bc61f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.spec.ts @@ -0,0 +1,70 @@ +/*! + * @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 { it, describe, expect } from '@angular/core/testing'; +import { FormModel } from './form.model'; +import { TabModel } from './tab.model'; +import { ContainerModel } from './container.model'; + +describe('TabModel', () => { + + it('should setup with json config', () => { + let json = { + id: '', + title: '', + visibilityCondition: '<condition>' + }; + + let model = new TabModel(null, json); + expect(model.id).toBe(json.id); + expect(model.title).toBe(json.title); + expect(model.visibilityCondition).toBe(json.visibilityCondition); + }); + + it('should not setup with json config', () => { + let model = new TabModel(null, null); + expect(model.id).toBeUndefined(); + expect(model.title).toBeUndefined(); + expect(model.visibilityCondition).toBeUndefined(); + }); + + it('should evaluate content based on fields', () => { + let model = new TabModel(null, null); + + model.fields = null; + expect(model.hasContent()).toBeFalsy(); + + model.fields = []; + expect(model.hasContent()).toBeFalsy(); + + model.fields = [new ContainerModel(null, null)]; + expect(model.hasContent()).toBeTruthy(); + }); + + it('should store the form reference', () => { + let form = new FormModel(); + let model = new TabModel(form); + expect(model.form).toBe(form); + }); + + it('should store original json', () => { + let json = {}; + let model = new TabModel(null, json); + expect(model.json).toBe(json); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.ts new file mode 100644 index 0000000000..e1932ba1f6 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/tab.model.ts @@ -0,0 +1,43 @@ +/*! + * @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 { FormWidgetModel } from './form-widget.model'; +import { ContainerModel } from './container.model'; +import { FormModel } from './form.model'; + +export class TabModel extends FormWidgetModel { + + id: string; + title: string; + visibilityCondition: any; + + fields: ContainerModel[] = []; + + hasContent(): boolean { + return this.fields && this.fields.length > 0; + } + + constructor(form: FormModel, json?: any) { + super(form, json); + + if (json) { + this.id = json.id; + this.title = json.title; + this.visibilityCondition = json.visibilityCondition; + } + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.css new file mode 100644 index 0000000000..40e3385ccb --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.css @@ -0,0 +1 @@ +.display-text-widget {} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.html new file mode 100644 index 0000000000..cc784e807d --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.html @@ -0,0 +1,3 @@ +<div class="display-text-widget"> + <span>{{field.value}}</span> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.ts new file mode 100644 index 0000000000..2f39c56db3 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-text/display-text.widget.ts @@ -0,0 +1,32 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'display-text-widget', + templateUrl: './display-text.widget.html', + styleUrls: ['./display-text.widget.css'] +}) +export class DisplayTextWidget extends WidgetComponent { + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.css new file mode 100644 index 0000000000..c3b7a552c9 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.css @@ -0,0 +1,3 @@ +.display-value-widget { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.html new file mode 100644 index 0000000000..a5d637d91c --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.html @@ -0,0 +1,10 @@ +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label display-value-widget"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [(ngModel)]="field.value" + (ngModelChange)="checkVisibility(field)" + disabled> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> +</div> + diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.ts new file mode 100644 index 0000000000..a095b85024 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.ts @@ -0,0 +1,32 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'display-value-widget', + templateUrl: './display-value.widget.html', + styleUrls: ['./display-value.widget.css'] +}) +export class DisplayValueWidget extends WidgetComponent { + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.css new file mode 100644 index 0000000000..f1be2ed3ba --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.css @@ -0,0 +1,7 @@ +.dropdown-widget { + width: 100%; +} + +.dropdown-widget > select { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.html new file mode 100644 index 0000000000..a6c3d50a94 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.html @@ -0,0 +1,6 @@ +<div class="dropdown-widget"> + <label [attr.for]="field.id">{{field.name}}</label> + <select [(ngModel)]="field.value" (ngModelChange)="checkVisibility(field)"> + <option *ngFor="let opt of field.options" [value]="opt.id">{{opt.name}}</option> + </select> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.spec.ts new file mode 100644 index 0000000000..2fdaf83374 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.spec.ts @@ -0,0 +1,212 @@ +/*! + * @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 { it, describe, expect, beforeEach } from '@angular/core/testing'; +import { Http, RequestOptionsArgs, Response, ResponseOptions } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; +import { DropdownWidget } from './dropdown.widget'; +import { FormModel } from './../core/form.model'; +import { FormFieldModel } from './../core/form-field.model'; + +describe('DropdownWidget', () => { + + let http: Http; + let widget: DropdownWidget; + + beforeEach(() => { + http = <Http> { + get(url: string, options?: RequestOptionsArgs): Observable<Response> { + return null; + } + }; + widget = new DropdownWidget(http); + }); + + it('should fetch and parse REST data on init', () => { + + let data = [ + { uid: '1', text: 'One' }, + { uid: '2', text: 'Two' } + ]; + + spyOn(http, 'get').and.callFake((url) => { + return Observable.create(observer => { + let options = new ResponseOptions({ + body: data, + url: url + }); + let response = new Response(options); + observer.next(response); + observer.complete(); + }); + }); + + let field = new FormFieldModel(new FormModel(), { + optionType: 'rest', + restUrl: 'http://<address>', + restIdProperty: 'uid', + restLabelProperty: 'text' + }); + + widget.field = field; + widget.ngOnInit(); + + + expect((<any>http.get).calls.argsFor(0)).toEqual([field.restUrl]); + expect(field.options.length).toBe(2); + + expect(field.options[0].id).toBe(data[0].uid); + expect(field.options[0].name).toBe(data[0].text); + + expect(field.options[1].id).toBe(data[1].uid); + expect(field.options[1].name).toBe(data[1].text); + }); + + it('should require REST settings to fetch data', () => { + let form = new FormModel(); + spyOn(http, 'get').and.stub(); + + // 1) Null field + widget.field = null; + widget.ngOnInit(); + expect(http.get).not.toHaveBeenCalled(); + + // 2) Missing [optionType] + widget.field = new FormFieldModel(form, { + optionType: null, + restUrl: 'http://<address>', + restIdProperty: 'uid', + restLabelProperty: 'text' + }); + widget.ngOnInit(); + expect(http.get).not.toHaveBeenCalled(); + + // 3) Missing [restUrl] + widget.field = new FormFieldModel(form, { + optionType: 'rest', + restUrl: null, + restIdProperty: 'uid', + restLabelProperty: 'text' + }); + widget.ngOnInit(); + expect(http.get).not.toHaveBeenCalled(); + + // 4) Missing [restIdProperty] + widget.field = new FormFieldModel(form, { + optionType: 'rest', + restUrl: 'http://<address>', + restIdProperty: null, + restLabelProperty: 'text' + }); + widget.ngOnInit(); + expect(http.get).not.toHaveBeenCalled(); + + // 4) Missing [restLabelProperty] + widget.field = new FormFieldModel(form, { + optionType: 'rest', + restUrl: 'http://<address>', + restIdProperty: null, + restLabelProperty: null + }); + widget.ngOnInit(); + expect(http.get).not.toHaveBeenCalled(); + }); + + it('should parse only array response', () => { + expect(widget.loadFromJson([])).toBeFalsy(); + + widget.field = new FormFieldModel(new FormModel()); + expect(widget.loadFromJson([])).toBeTruthy(); + + expect(widget.loadFromJson(null)).toBeFalsy(); + expect(widget.loadFromJson({})).toBeFalsy(); + }); + + it('should bind to nested properties', () => { + let data = [ + { uid: { value: 1 }, name: { fullName: 'John Doe' } } + ]; + + spyOn(http, 'get').and.callFake((url) => { + return Observable.create(observer => { + let options = new ResponseOptions({ + body: data, + url: url + }); + let response = new Response(options); + observer.next(response); + observer.complete(); + }); + }); + + let field = new FormFieldModel(new FormModel(), { + optionType: 'rest', + restUrl: 'http://<address>', + restIdProperty: 'uid.value', + restLabelProperty: 'name.fullName' + }); + + widget.field = field; + widget.ngOnInit(); + + expect(field.options.length).toBe(1); + expect(field.options[0].id).toBe(data[0].uid.value.toString()); + expect(field.options[0].name).toBe(data[0].name.fullName); + }); + + it('should update form upon loading REST data', () => { + let field = new FormFieldModel(new FormModel()); + widget.field = field; + + spyOn(field, 'updateForm').and.stub(); + + expect(widget.loadFromJson([])).toBeTruthy(); + expect(field.updateForm).toHaveBeenCalled(); + }); + + it('should handle error with generic message', () => { + spyOn(console, 'error').and.stub(); + + widget.handleError(null); + expect(console.error).toHaveBeenCalledWith(DropdownWidget.UNKNOWN_ERROR_MESSAGE); + }); + + it('should handle error with error message', () => { + spyOn(console, 'error').and.stub(); + + const message = '<error>'; + widget.handleError({ message: message }); + + expect(console.error).toHaveBeenCalledWith(message); + }); + + it('should handle error with detailed message', () => { + spyOn(console, 'error').and.stub(); + widget.handleError({ + status: '400', + statusText: 'Bad request' + }); + expect(console.error).toHaveBeenCalledWith('400 - Bad request'); + }); + + it('should handle error with generic message', () => { + spyOn(console, 'error').and.stub(); + widget.handleError({}); + expect(console.error).toHaveBeenCalledWith(DropdownWidget.GENERIC_ERROR_MESSAGE); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.ts new file mode 100644 index 0000000000..5e47fd41b9 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.ts @@ -0,0 +1,85 @@ +/*! + * @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, OnInit } from '@angular/core'; +import { Http } from '@angular/http'; +import { ObjectUtils } from 'ng2-alfresco-core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'dropdown-widget', + templateUrl: './dropdown.widget.html', + styleUrls: ['./dropdown.widget.css'] +}) +export class DropdownWidget extends WidgetComponent implements OnInit { + + static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error'; + static GENERIC_ERROR_MESSAGE: string = 'Server error'; + + constructor(private http: Http) { + super(); + } + + ngOnInit() { + if (this.field && + this.field.optionType === 'rest' && + this.field.restUrl && + this.field.restIdProperty && + this.field.restLabelProperty) { + + let url = `${this.field.restUrl}`; + this.http.get(url).subscribe( + response => { + let json: any = response.json(); + this.loadFromJson(json); + }, + this.handleError + ); + } + } + + // TODO: support 'restResponsePath' + loadFromJson(json: any): boolean { + if (this.field && json && json instanceof Array) { + let options = json.map(obj => { + return { + id: ObjectUtils.getValue(obj, this.field.restIdProperty).toString(), + name: ObjectUtils.getValue(obj, this.field.restLabelProperty).toString() + }; + }); + this.field.options = options; + this.field.updateForm(); + return true; + } + return false; + } + + + handleError(error: any) { + let errMsg = DropdownWidget.UNKNOWN_ERROR_MESSAGE; + if (error) { + errMsg = (error.message) ? error.message : + error.status ? `${error.status} - ${error.statusText}` : DropdownWidget.GENERIC_ERROR_MESSAGE; + } + console.error(errMsg); + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.css new file mode 100644 index 0000000000..f965e43936 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.css @@ -0,0 +1 @@ +.hyperlink-widget {} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.html new file mode 100644 index 0000000000..68717afde2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.html @@ -0,0 +1,3 @@ +<div class="hyperlink-widget"> + <a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.spec.ts new file mode 100644 index 0000000000..9b6be175ae --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.spec.ts @@ -0,0 +1,97 @@ +/*! + * @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 { it, describe, expect, beforeEach } from '@angular/core/testing'; +import { HyperlinkWidget } from './hyperlink.widget'; +import { FormModel } from './../core/form.model'; +import { FormFieldModel } from './../core/form-field.model'; + +describe('HyperlinkWidget', () => { + + let widget: HyperlinkWidget; + + beforeEach(() => { + widget = new HyperlinkWidget(); + }); + + it('should get link text from field display text', () => { + const text = 'hello world'; + + widget.field = new FormFieldModel(new FormModel(), { + displayText: text + }); + + expect(widget.linkText).toBe(text); + }); + + it('should get link text from field url', () => { + const url = 'http://<address>'; + + widget.field = new FormFieldModel(new FormModel(), { + displayText: null, + hyperlinkUrl: url + }); + + expect(widget.linkText).toBe(url); + }); + + it('should require field to get link text', () => { + widget.field = null; + expect(widget.linkText).toBeNull(); + }); + + it('should not return link text', () => { + widget.field = new FormFieldModel(new FormModel(), { + displayText: null, + hyperlinkUrl: null + }); + + expect(widget.linkText).toBeNull(); + }); + + it('should return default url for missing field', () => { + widget.field = null; + expect(widget.linkUrl).toBe(HyperlinkWidget.DEFAULT_URL); + }); + + it('should return default url for missing field property', () => { + widget.field = new FormFieldModel(new FormModel(), { + hyperlinkUrl: null + }); + + expect(widget.linkUrl).toBe(HyperlinkWidget.DEFAULT_URL); + }); + + it('should prepend url with scheme', () => { + const url = 'www.alfresco.com'; + widget.field = new FormFieldModel(new FormModel(), { + hyperlinkUrl: url + }); + + expect(widget.linkUrl).toBe(`${HyperlinkWidget.DEFAULT_URL_SCHEME}${url}`); + }); + + it('should not prepend url with scheme', () => { + const url = 'https://<secure/address>'; + widget.field = new FormFieldModel(new FormModel(), { + hyperlinkUrl: url + }); + + expect(widget.linkUrl).toBe(url); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.ts new file mode 100644 index 0000000000..dd9c7e3fd2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/hyperlink/hyperlink.widget.ts @@ -0,0 +1,55 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'hyperlink-widget', + templateUrl: './hyperlink.widget.html', + styleUrls: ['./hyperlink.widget.css'] +}) +export class HyperlinkWidget extends WidgetComponent { + + static DEFAULT_URL: string = '#'; + static DEFAULT_URL_SCHEME: string = 'http://'; + + get linkUrl(): string { + let url = HyperlinkWidget.DEFAULT_URL; + + if (this.field && this.field.hyperlinkUrl) { + url = this.field.hyperlinkUrl; + if (!/^https?:\/\//i.test(url)) { + url = HyperlinkWidget.DEFAULT_URL_SCHEME + url; + } + } + + return url; + } + + get linkText(): string { + if (this.field) { + return this.field.displayText || this.field.hyperlinkUrl; + } + return null; + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/index.ts b/ng2-components/ng2-activiti-form/src/components/widgets/index.ts new file mode 100644 index 0000000000..51ceb74e05 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/index.ts @@ -0,0 +1,70 @@ +/*! + * @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 { TabsWidget } from './tabs/tabs.widget'; +import { ContainerWidget } from './container/container.widget'; + +import { TextWidget } from './text/text.widget'; +import { NumberWidget } from './number/number.widget'; +import { CheckboxWidget } from './checkbox/checkbox.widget'; +import { MultilineTextWidget } from './multiline-text/multiline-text.widget'; +import { DropdownWidget } from './dropdown/dropdown.widget'; +import { HyperlinkWidget } from './hyperlink/hyperlink.widget'; +import { RadioButtonsWidget } from './radio-buttons/radio-buttons.widget'; +import { DisplayValueWidget } from './display-value/display-value.widget'; +import { DisplayTextWidget } from './display-text/display-text.widget'; +import { UploadWidget } from './upload/upload.widget'; + +// core +export * from './widget.component'; +export * from './core/index'; + +// containers +export * from './tabs/tabs.widget'; +export * from './container/container.widget'; + +// primitives +export * from './text/text.widget'; +export * from './number/number.widget'; +export * from './checkbox/checkbox.widget'; +export * from './multiline-text/multiline-text.widget'; +export * from './dropdown/dropdown.widget'; +export * from './hyperlink/hyperlink.widget'; +export * from './radio-buttons/radio-buttons.widget'; +export * from './display-value/display-value.widget'; +export * from './display-text/display-text.widget'; +export * from './upload/upload.widget'; + +export const CONTAINER_WIDGET_DIRECTIVES: [any] = [ + TabsWidget, + ContainerWidget +]; + +export const PRIMITIVE_WIDGET_DIRECTIVES: [any] = [ + TextWidget, + NumberWidget, + CheckboxWidget, + MultilineTextWidget, + DropdownWidget, + HyperlinkWidget, + RadioButtonsWidget, + DisplayValueWidget, + DisplayTextWidget, + UploadWidget +]; + + diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.css new file mode 100644 index 0000000000..9c7ec9fb37 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.css @@ -0,0 +1,3 @@ +:host .multiline-text-widget { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.html new file mode 100644 index 0000000000..2511b63434 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.html @@ -0,0 +1,11 @@ +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label multiline-text-widget"> + <textarea class="mdl-textfield__input" + type="text" + rows= "3" + [attr.id]="field.id" + [(ngModel)]="field.value" + (ngModelChange)="checkVisibility(field)" + [disabled]="field.readOnly"> + </textarea> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.ts new file mode 100644 index 0000000000..d269746c80 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.ts @@ -0,0 +1,32 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'multiline-text-widget', + templateUrl: './multiline-text.widget.html', + styleUrls: ['./multiline-text.widget.css'] +}) +export class MultilineTextWidget extends WidgetComponent { + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.css new file mode 100644 index 0000000000..eef6cf2a3f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.css @@ -0,0 +1,3 @@ +:host .number-widget { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.html new file mode 100644 index 0000000000..3a5bd8b474 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.html @@ -0,0 +1,11 @@ +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label number-widget"> + <input class="mdl-textfield__input" + type="text" + pattern="-?[0-9]*(\.[0-9]+)?" + [attr.id]="field.id" + [(ngModel)]="field.value" + (ngModelChange)="checkVisibility(field)" + [disabled]="field.readOnly"> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + <span class="mdl-textfield__error">Input is not a number!</span> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.ts new file mode 100644 index 0000000000..cf4170ae1a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.ts @@ -0,0 +1,32 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'number-widget', + templateUrl: './number.widget.html', + styleUrls: ['./number.widget.css'] +}) +export class NumberWidget extends WidgetComponent { + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.css new file mode 100644 index 0000000000..0fe4eb9fe7 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.css @@ -0,0 +1 @@ +.radio-buttons-widget {} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.html new file mode 100644 index 0000000000..a832a925f2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.html @@ -0,0 +1,15 @@ +<div class="radio-buttons-widget"> + <div *ngFor="let opt of field.options"> + <label [attr.for]="opt.id" class="mdl-radio mdl-js-radio"> + <input type="radio" + class="mdl-radio__button" + [checked]="field.value === opt.id" + [attr.id]="opt.id" + [attr.name]="field.id" + [attr.value]="opt.id" + [disabled]="field.readOnly" + (click)="field.value = opt.id"> + <span class="mdl-radio__label">{{opt.name}}</span> + </label> + </div> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.ts new file mode 100644 index 0000000000..e528813c72 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.ts @@ -0,0 +1,32 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'radio-buttons-widget', + templateUrl: './radio-buttons.widget.html', + styleUrls: ['./radio-buttons.widget.css'] +}) +export class RadioButtonsWidget extends WidgetComponent { + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.html new file mode 100644 index 0000000000..4c2f2cd5cb --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.html @@ -0,0 +1,20 @@ +<div *ngIf="hasTabs()" class="alfresco-tabs-widget"> + <div alfresco-mdl-tabs> + <div class="mdl-tabs__tab-bar"> + <a *ngFor="let tab of tabs; let isFirst = first" + [href]="'#' + tab.id" + class="mdl-tabs__tab" [class.is-active]="isFirst"> + {{tab.title}} + </a> + </div> + <div *ngFor="let tab of tabs; let isFirst = first" + class="mdl-tabs__panel" + [class.is-active]="isFirst" + [attr.id]="tab.id"> + <container-widget + *ngFor="let field of tab.fields" + [content]="field"> + </container-widget> + </div> + </div> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.spec.ts new file mode 100644 index 0000000000..2ffc23f974 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.spec.ts @@ -0,0 +1,60 @@ +/*! + * @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 { it, describe, expect, beforeEach } from '@angular/core/testing'; +import { TabsWidget } from './tabs.widget'; +import { TabModel } from './../core/tab.model'; + +describe('TabsWidget', () => { + + let componentHandler; + let widget: TabsWidget; + + beforeEach(() => { + widget = new TabsWidget(); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + }); + + it('should check tabs', () => { + widget.tabs = null; + expect(widget.hasTabs()).toBeFalsy(); + + widget.tabs = []; + expect(widget.hasTabs()).toBeFalsy(); + + widget.tabs = [new TabModel(null)]; + expect(widget.hasTabs()).toBeTruthy(); + }); + + it('should upgrade MDL content on view init', () => { + widget.ngAfterViewInit(); + expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled(); + }); + + it('should setup MDL content only if component handler available', () => { + expect(widget.setupMaterialComponents()).toBeTruthy(); + + window['componentHandler'] = null; + expect(widget.setupMaterialComponents()).toBeFalsy(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.ts new file mode 100644 index 0000000000..8c4d9de949 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.ts @@ -0,0 +1,53 @@ +/*! + * @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, Input, AfterViewInit } from '@angular/core'; +import { MATERIAL_DESIGN_DIRECTIVES } from 'ng2-alfresco-core'; +import { TabModel } from './../core/index'; +import { ContainerWidget } from './../container/container.widget'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'tabs-widget', + templateUrl: './tabs.widget.html', + directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget] +}) +export class TabsWidget implements AfterViewInit { + + @Input() + tabs: TabModel[] = []; + + hasTabs() { + return this.tabs && this.tabs.length > 0; + } + + ngAfterViewInit() { + this.setupMaterialComponents(); + } + + setupMaterialComponents(): boolean { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + return true; + } + return false; + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.css new file mode 100644 index 0000000000..8fe1b900e1 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.css @@ -0,0 +1,3 @@ +:host .text-widget { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.html new file mode 100644 index 0000000000..d74aac765a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.html @@ -0,0 +1,9 @@ +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label text-widget"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [(ngModel)]="field.value" + (ngModelChange)="checkVisibility(field)" + [disabled]="field.readOnly"> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.ts new file mode 100644 index 0000000000..44fc7c8494 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.ts @@ -0,0 +1,32 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'text-widget', + templateUrl: './text.widget.html', + styleUrls: ['./text.widget.css'] +}) +export class TextWidget extends WidgetComponent { + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.css new file mode 100644 index 0000000000..1735deb447 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.css @@ -0,0 +1,17 @@ +.upload-widget { + width:100% +} + +.upload-widget__icon { + float: left; +} + +.upload-widget__file { + float: left; + margin-top: 4px; +} + +.upload-widget__reset { + float: left; + margin-top: 4px; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.html new file mode 100644 index 0000000000..f5fdaabb25 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.html @@ -0,0 +1,15 @@ +<div class="upload-widget"> + + <label [attr.for]="field.id">{{field.name}}</label> + <div> + <i class="material-icons upload-widget__icon">image</i> + <span *ngIf="hasFile" class="upload-widget__file">{{getUploadedFileName()}}</span> + <input *ngIf="!hasFile" + #file + type="file" + [attr.id]="field.id" + class="upload-widget__file" + (change)="onFileChanged($event)"> + <button *ngIf="hasFile" (click)="reset(file);" class="upload-widget__reset">X</button> + </div> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.ts new file mode 100644 index 0000000000..2edf60dd6c --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.ts @@ -0,0 +1,105 @@ +/*! + * @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, OnInit } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; +import { AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; + +declare let __moduleName: string; +declare var componentHandler; + +@Component({ + moduleId: __moduleName, + selector: 'upload-widget', + templateUrl: './upload.widget.html', + styleUrls: ['./upload.widget.css'] +}) +export class UploadWidget extends WidgetComponent implements OnInit { + + constructor(private settingsService: AlfrescoSettingsService, + private authService: AlfrescoAuthenticationService) { + super(); + } + + hasFile: boolean; + fileName: string; + + ngOnInit() { + if (this.field && + this.field.value && + this.field.value.length > 0) { + this.hasFile = true; + } + } + + getUploadedFileName(): string { + let result = this.fileName; + + if (this.field && + this.field.value && + this.field.value.length > 0) { + let file = this.field.value[0]; + result = file.name; + } + + return result; + } + + reset() { + this.field.value = null; + this.field.json.value = null; + this.hasFile = false; + } + + onFileChanged(event: any) { + let files = event.srcElement.files; + if (files && files.length > 0) { + + let file = files[0]; + + this.hasFile = true; + this.fileName = file.name; + + let formData: FormData = new FormData(); + formData.append('file', file, file.name); + + let xhr: XMLHttpRequest = new XMLHttpRequest(); + + xhr.withCredentials = true; + + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let jsonFile = JSON.parse(xhr.response); + console.log(jsonFile); + this.field.value = [jsonFile]; + this.field.json.value = [jsonFile]; + } else { + console.error(xhr.response); + window.alert('Error uploading file. See console output for more details.'); + } + } + }; + + let url = `${this.settingsService.bpmHost}/activiti-app/app/rest/content/raw`; + xhr.open('POST', url, true); + xhr.setRequestHeader('Authorization', this.authService.getTicketBpm()); + xhr.send(formData); + } + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.spec.ts new file mode 100644 index 0000000000..849e84a255 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.spec.ts @@ -0,0 +1,86 @@ +/*! + * @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 { it, describe, expect, beforeEach } from '@angular/core/testing'; +import { WidgetComponent } from './widget.component'; +import { FormFieldModel } from './core/form-field.model'; +import { FormModel } from './core/form.model'; + +describe('WidgetComponent', () => { + + let componentHandler; + + beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + }); + + it('should upgrade MDL content on view init', () => { + let component = new WidgetComponent(); + component.ngAfterViewInit(); + expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled(); + }); + + it('should setup MDL content only if component handler available', () => { + let component = new WidgetComponent(); + expect(component.setupMaterialComponents()).toBeTruthy(); + + window['componentHandler'] = null; + expect(component.setupMaterialComponents()).toBeFalsy(); + }); + + it('should check field', () => { + let component = new WidgetComponent(); + + expect(component.hasField()).toBeFalsy(); + component.field = new FormFieldModel(null); + expect(component.hasField()).toBeTruthy(); + }); + + it('should send an event after view init', (done) => { + let component = new WidgetComponent(); + let fakeForm = new FormModel(); + let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'}); + component.field = fakeField; + + component.fieldChanged.subscribe(field => { + expect(field).not.toBe(null); + expect(field.id).toBe('fakeField'); + expect(field.value).toBe('fakeValue'); + done(); + }); + + component.ngAfterViewInit(); + }); + + it('should send an event when a field is changed', (done) => { + let component = new WidgetComponent(); + let fakeForm = new FormModel(); + let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'}); + component.fieldChanged.subscribe(field => { + expect(field).not.toBe(null); + expect(field.id).toBe('fakeField'); + expect(field.value).toBe('fakeValue'); + done(); + }); + + component.checkVisibility(fakeField); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts new file mode 100644 index 0000000000..b27efe1214 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts @@ -0,0 +1,57 @@ +/*! + * @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 { Input, AfterViewInit, Output, EventEmitter } from '@angular/core'; +import { FormFieldModel } from './core/index'; + +declare let __moduleName: string; +declare var componentHandler; + +/** + * Base widget component. + */ +export class WidgetComponent implements AfterViewInit { + + @Input() + field: FormFieldModel; + + @Output() + fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>(); + + hasField() { + return this.field ? true : false; + } + + ngAfterViewInit() { + this.setupMaterialComponents(); + this.fieldChanged.emit(this.field); + } + + setupMaterialComponents(): boolean { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + return true; + } + return false; + } + + checkVisibility(field: FormFieldModel) { + this.fieldChanged.emit(field); + } + +} diff --git a/ng2-components/ng2-activiti-form/src/models/form-definition.model.ts b/ng2-components/ng2-activiti-form/src/models/form-definition.model.ts new file mode 100644 index 0000000000..fd7fd95b8e --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/models/form-definition.model.ts @@ -0,0 +1,95 @@ +/*! + * @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 FormDefinitionModel { + reusable: boolean = false; + newVersion: boolean = false; + formRepresentation: any; + formImageBase64: string = ''; + + constructor(id: string, name: any, lastUpdatedByFullName: string, lastUpdated: string, metadata: any) { + + this.formRepresentation = { + id: id, + name: name, + description: '', + version: 1, + lastUpdatedBy: 1, + lastUpdatedByFullName: lastUpdatedByFullName, + lastUpdated: lastUpdated, + stencilSetId: 0, + referenceId: null, + formDefinition: { + fields: [{ + name: 'Label', + type: 'container', + fieldType: 'ContainerRepresentation', + numberOfColumns: 2, + required: false, + readOnly: false, + sizeX: 2, + sizeY: 1, + row: -1, + col: -1, + fields: {'1': this.metadataToFields(metadata)} + }], + gridsterForm: false, + javascriptEvents: [], + metadata: {}, + outcomes: [], + className: '', + style: '', + tabs: [], + variables: [] + } + }; + } + + private metadataToFields(metadata: any): any[] { + let fields = []; + if (metadata) { + metadata.forEach(function(property) { + if (property) { + let field = { + type: 'text', + id: property.name, + name: property.name, + required: false, + readOnly: false, + sizeX: 1, + sizeY: 1, + row: -1, + col: -1, + colspan: 1, + params: { + existingColspan: 1, + maxColspan: 2 + }, + layout: { + colspan: 1, + row: -1, + column: -1 + } + }; + fields.push(field); + } + }); + } + + return fields; + } +} diff --git a/ng2-components/ng2-activiti-form/src/models/node-metadata.model.ts b/ng2-components/ng2-activiti-form/src/models/node-metadata.model.ts new file mode 100644 index 0000000000..35254e8a7b --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/models/node-metadata.model.ts @@ -0,0 +1,26 @@ +/*! + * @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 NodeMetadata { + metadata: any; + nodeType: string; + + constructor(metadata: any, nodeType: string) { + this.metadata = metadata; + this.nodeType = nodeType; + } +} diff --git a/ng2-components/ng2-activiti-form/src/models/task-process-variable.model.ts b/ng2-components/ng2-activiti-form/src/models/task-process-variable.model.ts new file mode 100644 index 0000000000..d6966a17aa --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/models/task-process-variable.model.ts @@ -0,0 +1,22 @@ +/*! + * @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 TaskProcessVariableModel { + id: string; + type: string; + value: string; +} diff --git a/ng2-components/ng2-activiti-form/src/models/widget-visibility.model.ts b/ng2-components/ng2-activiti-form/src/models/widget-visibility.model.ts new file mode 100644 index 0000000000..e6cf0dec4a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/models/widget-visibility.model.ts @@ -0,0 +1,28 @@ +/*! + * @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 WidgetVisibilityModel { + leftFormFieldId: string; + leftRestResponseId: string; + nextCondition: WidgetVisibilityModel; + nextConditionOperator: string; + operator: string; + rightFormFieldId: string; + rightRestResponseId: string; + rightType: string; + rightValue: string; +} diff --git a/ng2-components/ng2-activiti-form/src/services/ecm-model.service.ts b/ng2-components/ng2-activiti-form/src/services/ecm-model.service.ts new file mode 100644 index 0000000000..9c9f4437d3 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/ecm-model.service.ts @@ -0,0 +1,262 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { Observable } from 'rxjs/Rx'; +import { Response, Http, Headers, RequestOptions } from '@angular/http'; +import { FormModel } from '../components/widgets/core/form.model'; +import { NodeService } from './node.service'; + +@Injectable() +export class EcmModelService { + + public static MODEL_NAMESPACE: string = 'activitiForms'; + public static MODEL_NAME: string = 'activitiFormsModel'; + public static TYPE_MODEL: string = 'cm:folder'; + + constructor(private authService: AlfrescoAuthenticationService, + private http: Http, + public alfrescoSettingsService: AlfrescoSettingsService, + private nodeService: NodeService) { + } + + public createEcmTypeForActivitiForm(formName: string, form: FormModel): Observable<any> { + return Observable.create(observer => { + this.seachActivitiEcmModel().subscribe( + model => { + if (!model) { + this.createActivitiEcmModel(formName, form).subscribe(typeForm => { + observer.next(typeForm); + observer.complete(); + }); + } else { + this.createFomType(formName, form).subscribe(typeForm => { + observer.next(typeForm); + observer.complete(); + }); + } + }, + this.handleError + ); + }); + + } + + private seachActivitiEcmModel() { + return this.getEcmModels().map(function (ecmModels: any) { + return ecmModels.list.entries.find(model => model.entry.name === EcmModelService.MODEL_NAME); + }); + } + + private createActivitiEcmModel(formName: string, form: FormModel): Observable<any> { + return Observable.create(observer => { + this.createEcmModel(EcmModelService.MODEL_NAME, EcmModelService.MODEL_NAMESPACE).subscribe( + model => { + console.log('model created', model); + this.activeEcmModel(EcmModelService.MODEL_NAME).subscribe( + modelActive => { + console.log('model active', modelActive); + this.createEcmTypeWithProperties(formName, form).subscribe(typeCreated => { + observer.next(typeCreated); + observer.complete(); + }); + }, + this.handleError + ); + }, + this.handleError + ); + }); + } + + private createFomType(formName: string, form: FormModel): Observable<any> { + return Observable.create(observer => { + this.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe( + ecmType => { + console.log('custom types', ecmType); + if (!ecmType) { + this.createEcmTypeWithProperties(formName, form).subscribe(typeCreated => { + observer.next(typeCreated); + observer.complete(); + }); + } else { + observer.next(ecmType); + observer.complete(); + } + }, + this.handleError + ); + }); + } + + public createEcmTypeWithProperties(formName: string, form: FormModel): Observable<any> { + return Observable.create(observer => { + this.createEcmType(formName, EcmModelService.MODEL_NAME, EcmModelService.TYPE_MODEL).subscribe( + typeCreated => { + console.log('type Created', typeCreated); + this.addPropertyToAType(EcmModelService.MODEL_NAME, formName, form).subscribe( + properyAdded => { + console.log('property Added', properyAdded); + observer.next(typeCreated); + observer.complete(); + }, + this.handleError); + }, + this.handleError); + }); + } + + public searchEcmType(typeName: string, modelName: string): Observable<any> { + return this.getEcmType(modelName).map(function (customTypes: any) { + return customTypes.list.entries.find(type => type.entry.prefixedName === typeName || type.entry.title === typeName); + }); + } + + public activeEcmModel(modelName: string): Observable<any> { + let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}?select=status`; + let options = this.getRequestOptions(); + + + let body = {status: 'ACTIVE'}; + + return this.http + .put(url, body, options) + .map(this.toJson) + .catch(this.handleError); + } + + public createEcmModel(modelName: string, nameSpace: string): Observable<any> { + let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm`; + let options = this.getRequestOptions(); + + + let body = { + status: 'DRAFT', namespaceUri: modelName, namespacePrefix: nameSpace, name: modelName, description: '', author: '' + }; + + return this.http + .post(url, body, options) + .map(this.toJson) + .catch(this.handleError); + } + + public getEcmModels(): Observable<any> { + let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm`; + let options = this.getRequestOptions(); + + return this.http + .get(url, options) + .map(this.toJson) + .catch(this.handleError); + } + + + public getEcmType(modelName: string): Observable<any> { + let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}/types`; + let options = this.getRequestOptions(); + + return this.http + .get(url, options) + .map(this.toJson) + .catch(this.handleError); + } + + public createEcmType(typeName: string, modelName: string, parentType: string): Observable<any> { + let name = this.cleanNameType(typeName); + let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}/types`; + let options = this.getRequestOptions(); + + let body = { + name: name, + parentName: parentType, + title: typeName, + description: '' + }; + + return this.http + .post(url, body, options) + .map(this.toJson) + .catch(this.handleError); + } + + public addPropertyToAType(modelName: string, typeName: string, formFields: any) { + let name = this.cleanNameType(typeName); + let url = `${this.alfrescoSettingsService.ecmHost}/alfresco/api/-default-/private/alfresco/versions/1/cmm/${modelName}/types/${name}?select=props`; + let options = this.getRequestOptions(); + + let properties = []; + if (formFields && formFields.values) { + for (let key in formFields.values) { + if (key) { + properties.push({ + name: key, + title: key, + description: key, + dataType: 'd:text', + multiValued: false, + mandatory: false, + mandatoryEnforced: false + }); + } + } + } + + let body = { + name: name, + properties: properties + }; + + return this.http + .put(url, body, options) + .map(this.toJson) + .catch(this.handleError); + } + + public cleanNameType(name: string): string { + let cleanName = name; + if (name.indexOf(':') !== -1) { + cleanName = name.split(':')[1]; + } + return cleanName.replace(/[^a-zA-Z ]/g, ''); + } + + public getHeaders(): Headers { + return new Headers({ + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': this.authService.getTicketEcmBase64() + }); + } + + public getRequestOptions(): RequestOptions { + let headers = this.getHeaders(); + return new RequestOptions({headers: headers}); + } + + toJson(res: Response) { + if (res) { + let body = res.json(); + return body || {}; + } + return {}; + } + + handleError(err: any): any { + console.log(err); + } +} diff --git a/ng2-components/ng2-activiti-form/src/services/form.service.spec.ts b/ng2-components/ng2-activiti-form/src/services/form.service.spec.ts new file mode 100644 index 0000000000..a93a0abe25 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/form.service.spec.ts @@ -0,0 +1,282 @@ +/*! + * @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 { it, inject, describe, expect, beforeEach, beforeEachProviders, afterEach } from '@angular/core/testing'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { HTTP_PROVIDERS, Response, ResponseOptions } from '@angular/http'; +import { FormService } from './form.service'; +import { EcmModelService } from './ecm-model.service'; +import { NodeService } from './node.service'; + +declare let jasmine: any; + +describe('FormService', () => { + + let responseBody: any, formService: FormService; + + beforeEachProviders(() => { + return [ + FormService, + AlfrescoSettingsService, + AlfrescoAuthenticationService, + EcmModelService, + HTTP_PROVIDERS, + NodeService + ]; + }); + + beforeEach(inject([FormService], (service: FormService) => { + jasmine.Ajax.install(); + formService = service; + })); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should fetch and parse process definitions', (done) => { + responseBody = { + data: [ + {id: '1'}, + {id: '2'} + ] + }; + + formService.getProcessDefinitions().subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/process-definitions')).toBeTruthy(); + expect(result).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should fetch and parse tasks', (done) => { + responseBody = { + data: [ + { id: '1' }, + { id: '2' } + ] + }; + + formService.getTasks().subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/query')).toBeTruthy(); + expect(result).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should fetch and parse the task by id', (done) => { + responseBody = { + id: '1' + }; + + formService.getTask('1').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/1')).toBeTruthy(); + expect(result.id).toEqual(responseBody.id); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should save task form', (done) => { + let values = { + field1: 'one', + field2: 'two' + }; + + formService.saveTaskForm('1', values).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1/save-form')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should complete task form', (done) => { + let values = { + field1: 'one', + field2: 'two' + }; + + formService.completeTaskForm('1', values).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should complete task form with a specific outcome', (done) => { + let values = { + field1: 'one', + field2: 'two' + }; + + formService.completeTaskForm('1', values, 'custom').subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).outcome).toEqual('custom' ); + + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should get task form by id', (done) => { + responseBody = { id: 1 }; + + formService.getTaskForm('1').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy(); + expect(result.id).toEqual(responseBody.id); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should get form definition by id', (done) => { + responseBody = { id: 1 }; + + formService.getFormDefinitionById('1').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/form-models/1')).toBeTruthy(); + expect(result.id).toEqual(responseBody.id); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should get form definition id by name', (done) => { + const formName = 'form1'; + const formId = 1; + responseBody = { + data: [ + { id: formId } + ] + }; + + formService.getFormDefinitionByName(formName).subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith(`models?filter=myReusableForms&filterText=${formName}&modelType=2`)).toBeTruthy(); + expect(result).toEqual(formId); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should not get form id from response', () => { + let response = new Response(new ResponseOptions({ body: null })); + expect(formService.getFormId(response)).toBeNull(); + + response = new Response(new ResponseOptions({ body: {} })); + expect(formService.getFormId(response)).toBeNull(); + + response = new Response(new ResponseOptions({ body: { data: null } })); + expect(formService.getFormId(response)).toBeNull(); + + response = new Response(new ResponseOptions({ body: { data: [] } })); + expect(formService.getFormId(response)).toBeNull(); + + expect(formService.getFormId(null)).toBeNull(); + }); + + it('should fallback to empty json array', () => { + expect(formService.toJsonArray(null)).toEqual([]); + + let response = new Response(new ResponseOptions({ body: {} })); + expect(formService.toJsonArray(response)).toEqual([]); + + response = new Response(new ResponseOptions({ body: { data: null } })); + expect(formService.toJsonArray(response)).toEqual([]); + }); + + it('should handle error with generic message', () => { + spyOn(console, 'error').and.stub(); + + formService.handleError(null); + expect(console.error).toHaveBeenCalledWith(FormService.UNKNOWN_ERROR_MESSAGE); + }); + + it('should handle error with error message', () => { + spyOn(console, 'error').and.stub(); + + const message = '<error>'; + formService.handleError({ message: message }); + + expect(console.error).toHaveBeenCalledWith(message); + }); + + it('should handle error with detailed message', () => { + spyOn(console, 'error').and.stub(); + formService.handleError({ + status: '400', + statusText: 'Bad request' + }); + expect(console.error).toHaveBeenCalledWith('400 - Bad request'); + }); + + it('should handle error with generic message', () => { + spyOn(console, 'error').and.stub(); + formService.handleError({}); + expect(console.error).toHaveBeenCalledWith(FormService.GENERIC_ERROR_MESSAGE); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/services/form.service.ts b/ng2-components/ng2-activiti-form/src/services/form.service.ts new file mode 100644 index 0000000000..a5f33bc003 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/form.service.ts @@ -0,0 +1,248 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { FormValues } from './../components/widgets/core/index'; +import { FormDefinitionModel } from '../models/form-definition.model'; +import { EcmModelService } from './ecm-model.service'; + +@Injectable() +export class FormService { + + static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error'; + static GENERIC_ERROR_MESSAGE: string = 'Server error'; + + constructor(private authService: AlfrescoAuthenticationService, + private ecmModelService: EcmModelService) { + } + + /** + * Create a Form with a fields for each metadata properties + * @returns {Observable<any>} + */ + public createFormFromNodeType(formName: string): Observable<any> { + return Observable.create(observer => { + this.createForm(formName).subscribe( + form => { + this.ecmModelService.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe( + customType => { + let formDefinitionModel = new FormDefinitionModel(form.id, form.name, form.lastUpdatedByFullName, form.lastUpdated, customType.entry.properties); + this.addFieldsNodeTypePropertiesToTheForm(form.id, formDefinitionModel).subscribe(formData => { + observer.next(formData); + observer.complete(); + }, this.handleError); + }, + this.handleError); + }, this.handleError); + }); + } + + /** + * Create a Form + * @returns {Observable<any>} + */ + public createForm(formName: string): Observable<any> { + let dataModel = { + name: formName, + description: '', + modelType: 2, + stencilSet: 0 + }; + + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.createModel(dataModel)); + } + + /** + * Add Fields to A form from a metadata properties + * @returns {Observable<any>} + */ + public addFieldsNodeTypePropertiesToTheForm(formId: string, formDefinitionModel: FormDefinitionModel): Observable<any> { + return this.addFieldsToAForm(formId, formDefinitionModel); + } + + /** + * Add Fileds to A form + * @returns {Observable<any>} + */ + public addFieldsToAForm(formId: string, formModel: FormDefinitionModel): Observable<any> { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.editorApi.saveForm(formId, formModel)); + } + + /** + * Search For A Form by name + * @returns {Observable<any>} + */ + public searchFrom(name: string): Observable<any> { + let opts = { + 'modelType': 2 + }; + + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)).map(function (forms: any) { + return forms.data.find(formdata => formdata.name === name); + }).catch(this.handleError); + } + + /** + * Get All the forms + * @returns {Observable<any>} + */ + public getForms(): Observable<any> { + let opts = { + 'modelType': 2 + }; + + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)); + } + + /** + * Get Process Definition + * @returns {Observable<any>} + */ + public getProcessDefinitions(): Observable<any> { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessDefinitions({})) + .map(this.toJsonArray) + .catch(this.handleError); + } + + /** + * Get All the Tasks + * @param taskId Task Id + * @returns {Observable<any>} + */ + public getTasks(): Observable<any> { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.listTasks({})) + .map(this.toJsonArray) + .catch(this.handleError); + } + + /** + * Get Task + * @param taskId Task Id + * @returns {Observable<any>} + */ + public getTask(taskId: string): Observable<any> { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTask(taskId)) + .map(this.toJson) + .catch(this.handleError); + } + + /** + * Save Task Form + * @param taskId Task Id + * @param formValues Form Values + * @returns {Observable<any>} + */ + public saveTaskForm(taskId: string, formValues: FormValues): Observable<any> { + let body = JSON.stringify({values: formValues}); + + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.saveTaskForm(taskId, body)) + .catch(this.handleError); + } + + /** + * Complete Task Form + * @param taskId Task Id + * @param formValues Form Values + * @param outcome Form Outcome + * @returns {Observable<any>} + */ + public completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> { + let data: any = {values: formValues}; + if (outcome) { + data.outcome = outcome; + } + let body = JSON.stringify(data); + + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.completeTaskForm(taskId, body)) + .catch(this.handleError); + } + + /** + * Get Form related to a taskId + * @param taskId Task Id + * @returns {Observable<any>} + */ + public getTaskForm(taskId: string): Observable<any> { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTaskForm(taskId)) + .map(this.toJson) + .catch(this.handleError); + } + + /** + * Get Form Definition + * @param formId Form Id + * @returns {Observable<any>} + */ + public getFormDefinitionById(formId: string): Observable<any> { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.editorApi.getForm(formId)) + .map(this.toJson) + .catch(this.handleError); + } + + /** + * Returns form definition ID by a given name. + * @param name + * @returns {Promise<T>|Promise<ErrorObservable>} + */ + public getFormDefinitionByName(name: string): Observable<any> { + let opts = { + 'filter': 'myReusableForms', + 'filterText': name, + 'modelType': 2 + }; + + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)) + .map(this.getFormId) + .catch(this.handleError); + } + + getFormId(res: any) { + let result = null; + + if (res && res.data && res.data.length > 0) { + result = res.data[0].id; + } + + return result; + } + + toJson(res: any) { + if (res) { + return res || {}; + } + return {}; + } + + toJsonArray(res: any) { + if (res) { + return res.data || []; + } + return []; + } + + handleError(error: any): Observable<any> { + let errMsg = FormService.UNKNOWN_ERROR_MESSAGE; + if (error) { + errMsg = (error.message) ? error.message : + error.status ? `${error.status} - ${error.statusText}` : FormService.GENERIC_ERROR_MESSAGE; + } + console.error(errMsg); + return Observable.throw(errMsg); + } +} diff --git a/ng2-components/ng2-activiti-form/src/services/node.service.ts b/ng2-components/ng2-activiti-form/src/services/node.service.ts new file mode 100644 index 0000000000..10a9c39815 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/node.service.ts @@ -0,0 +1,96 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { Observable } from 'rxjs/Rx'; +import { NodeMetadata } from '../models/node-metadata.model'; + +@Injectable() +export class NodeService { + + constructor(private authService: AlfrescoAuthenticationService) { + } + + /** + * Get All the metadata and the nodeType for a nodeId cleaned by the prefix + * @param nodeId Node Id + * @returns NodeMetadata + */ + public getNodeMetadata(nodeId: string): Observable<NodeMetadata> { + return Observable.fromPromise(this.authService.getAlfrescoApi().nodes.getNodeInfo(nodeId)).map(this.cleanMetadataFromSemicolon); + } + + /** + * Create a new Node from form metadata + * @param path path + * @param nodeType node type + * @param nameSpace namespace node + * @param data data to store + * @returns NodeMetadata + */ + public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable<any> { + let properties = {}; + for (let key in data) { + if (data[key]) { + properties[nameSpace + ':' + key] = data[key]; + } + } + + return this.createNode(name || this.generateUuid(), nodeType, properties, path); + } + + /** + * Create a new Node from form metadata + * @param name path + * @param nodeType node type + * @param properties namespace node + * @param path path + * @returns NodeMetadata + */ + public createNode(name: string, nodeType: string, properties: any, path: string): Observable<any> { + let body = { + name: name, + nodeType: nodeType, + properties: properties, + relativePath: path + }; + + return Observable.fromPromise(this.authService.getAlfrescoApi().nodes.addNode('-root-', body, {})); + } + + private generateUuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + + private cleanMetadataFromSemicolon(data: any): NodeMetadata { + let metadata = {}; + + if (data && data.properties) { + for (let key in data.properties) { + if (key) { + metadata [key.split(':')[1]] = data.properties[key]; + } + } + } + + return new NodeMetadata(metadata, data.nodeType); + } +} diff --git a/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.spec.ts b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.spec.ts new file mode 100644 index 0000000000..3e1b9f62b8 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.spec.ts @@ -0,0 +1,557 @@ +/*! + * @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 { it, describe, inject, beforeEach, beforeEachProviders } from '@angular/core/testing'; +import { WidgetVisibilityService } from './widget-visibility.service'; +import { AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { HTTP_PROVIDERS } from '@angular/http'; +import { WidgetVisibilityModel } from '../models/widget-visibility.model'; +import { TaskProcessVariableModel } from '../models/task-process-variable.model'; +import { FormModel, FormValues, FormFieldModel } from '../components/widgets/core/index'; + +declare let AlfrescoApi: any; +declare let jasmine: any; + +describe('WidgetVisibilityService', () => { + let service; + let formTest = new FormModel({}); + let formValues: FormValues = { 'test_1': 'value_1', 'test_2': 'value_2', 'test_3': 'value_1' }; + let fakeTaskProcessVariableModels = [ + {id: 'TEST_VAR_1', type: 'string', value: 'test_value_1'}, + {id: 'TEST_VAR_2', type: 'string', value: 'test_value_2'}, + {id: 'TEST_VAR_3', type: 'string', value: 'test_value_3'} + ]; + + let fakeFormJson = { + id: '9999', + name: 'FORM_VISIBILITY', + processDefinitionId: 'PROCESS_TEST:9:9999', + processDefinitionName: 'PROCESS_TEST', + processDefinitionKey: 'PROCESS_TEST', + taskId: '999', + taskName: 'TEST', + fields: [ + { + fieldType: 'ContainerRepresentation', + id: '000000000000000000', + name: 'Label', + type: 'container', + value: null, + numberOfColumns: 2, + fields: { + 1: [ + { + fieldType: 'FormFieldRepresentation', + id: 'FIELD_WITH_CONDITION', + name: 'FIELD_WITH_CONDITION', + type: 'text', + value: 'field_with_condition_value', + visibilityCondition: null + }, + { + fieldType: 'FormFieldRepresentation', + id: 'LEFT_FORM_FIELD_ID', + name: 'LEFT_FORM_FIELD_NAME', + type: 'text', + value: 'LEFT_FORM_FIELD_VALUE', + visibilityCondition: null + } + ], + 2: [ + { + fieldType: 'FormFieldRepresentation', + id: 'RIGHT_FORM_FIELD_ID', + name: 'RIGHT_FORM_FIELD_NAME', + type: 'text', + value: 'RIGHT_FORM_FIELD_VALUE', + visibilityCondition: null + } + ] + } + } + ] + }; + + beforeEachProviders(() => { + return [ + HTTP_PROVIDERS, + AlfrescoSettingsService, + AlfrescoAuthenticationService, + WidgetVisibilityService + ]; + }); + + beforeEach( + inject([WidgetVisibilityService], (activitiService: WidgetVisibilityService) => { + jasmine.Ajax.install(); + service = activitiService; + }) + ); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should return the process variables for task', (done) => { + service.getTaskProcessVariableModelsForTask(9999).subscribe( + (res: TaskProcessVariableModel[]) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(3); + expect(res[0].id).toEqual('TEST_VAR_1'); + expect(res[0].type).toEqual('string'); + expect(res[0].value).toEqual('test_value_1'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskProcessVariableModels) + }); + }); + + it('should evaluate logic operation for two values', () => { + let res: boolean; + + res = service.evaluateLogicalOperation( 'or', true, false); + + expect(res).toBeTruthy(); + + res = service.evaluateLogicalOperation( 'and', true, true); + + expect(res).toBeTruthy(); + + res = service.evaluateLogicalOperation( 'and not', true, false); + + expect(res).toBeTruthy(); + + res = service.evaluateLogicalOperation( 'or not', true, true ); + + expect(res).toBeTruthy(); + + res = service.evaluateLogicalOperation( 'or', false, false ); + + expect(res).toBeFalsy(); + + res = service.evaluateLogicalOperation( 'and', true, false ); + + expect(res).toBeFalsy(); + + res = service.evaluateLogicalOperation( 'and not', false, false ); + + expect(res).toBeFalsy(); + + res = service.evaluateLogicalOperation( 'or not', false, true ); + + expect(res).toBeFalsy(); + }); + + it('should evaluate string operation for two values', () => { + let res: boolean; + + res = service.evaluateCondition( 'test', 'test', '=='); + + expect(res).toBeTruthy(); + + res = service.evaluateCondition( 1, 2, '<'); + + expect(res).toBeTruthy(); + + res = service.evaluateCondition( true, false, '!=' ); + + expect(res).toBeTruthy(); + + res = service.evaluateCondition( 2, 3, '>' ); + + expect(res).toBeFalsy(); + + res = service.evaluateCondition( 2, 2, '>=' ); + + expect(res).toBeTruthy(); + + res = service.evaluateCondition( 4, 2, '<=' ); + + expect(res).toBeFalsy(); + + res = service.evaluateCondition( null, null, 'empty' ); + + expect(res).toBeTruthy(); + + res = service.evaluateCondition( '', '', 'empty' ); + + expect(res).toBeTruthy(); + + res = service.evaluateCondition( null, null, '!empty' ); + + expect(res).toBeFalsy(); + + res = service.evaluateCondition( '', '', '!empty' ); + + expect(res).toBeFalsy(); + }); + + it('should be able to retrieve the value of a process variable', (done) => { + service.getTaskProcessVariableModelsForTask(9999).subscribe( + (res: TaskProcessVariableModel[]) => { + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskProcessVariableModels) + }); + + + let varValue = service.getValueFromVariable(formTest, 'TEST_VAR_1'); + + expect(varValue).not.toBe(null); + expect(varValue).toBe('test_value_1'); + }); + + it('should be able to retrieve the value of a form variable', () => { + let fakeForm = new FormModel({variables: [ + { name: 'FORM_VARIABLE_TEST', + type: 'string', + value: 'form_value_test' } + ]}); + let varValue = service.getValueFromVariable(fakeForm, 'FORM_VARIABLE_TEST'); + + expect(varValue).not.toBe(null); + expect(varValue).toBe('form_value_test'); + }); + + /* + it('should return null if the variable does not exist', (done) => { + service.getTaskProcessVariableModelsForTask(9999).subscribe( + (res: TaskProcessVariableModel[]) => { + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskProcessVariableModels) + }); + + let varValue = service.getValueFromVariable(formTest, 'TEST_MYSTERY_VAR'); + + expect(varValue).toBe(null); + }); + */ + + it('should be able to retrieve a field value searching in the form', () => { + let stubFormWithFields = new FormModel(fakeFormJson); + let formValue = service.getFormValueByName(stubFormWithFields, 'FIELD_WITH_CONDITION'); + + expect(formValue).not.toBe(null); + expect(formValue).toBe('field_with_condition_value'); + }); + + it('should return null if the field value is not in the form', () => { + let stubFormWithFields = new FormModel(fakeFormJson); + + let formValue = service.getFormValueByName(stubFormWithFields, 'FIELD_MYSTERY'); + + expect(formValue).toBe(null); + }); + + it('should take the value from form values if it is present', () => { + formTest.values = formValues; + + let formValue = service.getValueOField(formTest, 'test_1'); + + expect(formValue).not.toBe(null); + expect(formValue).toBe('value_1'); + }); + + it('should search in the form if element value is not in form values', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + fakeFormWithField.values = formValues; + + let value = service.getValueOField(fakeFormWithField, 'FIELD_WITH_CONDITION'); + + expect(value).not.toBe(null); + expect(value).toBe('field_with_condition_value'); + }); + + it('should return null if the element is not present anywhere', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + fakeFormWithField.values = formValues; + + let formValue = service.getValueOField(fakeFormWithField, 'FIELD_MYSTERY'); + + expect(formValue).toBe(null); + }); + + it('should retrieve the value for the right field when it is a value', () => { + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.rightValue = '100'; + + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).toBe('100'); + }); + + it('should retrieve the value for the right field when it is a process variable', (done) => { + service.getTaskProcessVariableModelsForTask(9999).subscribe( + (res: TaskProcessVariableModel[]) => { + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskProcessVariableModels) + }); + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.rightRestResponseId = 'TEST_VAR_2'; + + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).not.toBe(null); + expect(rightValue).toBe('test_value_2'); + }); + + it('should retrieve the value for the right field when it is a form variable', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + + let rightValue = service.getRightValue(fakeFormWithField, visibilityObjTest); + + expect(rightValue).not.toBe(null); + expect(rightValue).toBe('RIGHT_FORM_FIELD_VALUE'); + }); + + it('should retrieve right value from form values if it is present', () => { + formTest.values = formValues; + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.rightFormFieldId = 'test_2'; + + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).not.toBe(null); + expect(formTest.values).toEqual(formValues); + expect(rightValue).toBe('value_2'); + }); + + it('should return null for a value that is not on variable or form', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + fakeFormWithField.values = formValues; + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.rightFormFieldId = 'NO_FIELD_FORM'; + + let rightValue = service.getRightValue(fakeFormWithField, visibilityObjTest); + + expect(rightValue).toBe(null); + }); + + /* + it('should retrieve the value for the left field when it is a process variable', (variableUpdated) => { + service.getTaskProcessVariableModelsForTask(9999).subscribe( + (res: TaskProcessVariableModel[]) => { + variableUpdated(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskProcessVariableModels) + }); + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftRestResponseId = 'TEST_VAR_2'; + + let rightValue = service.getLeftValue(formTest, visibilityObjTest); + + expect(rightValue).not.toBe(null); + expect(rightValue).toBe('test_value_2'); + }); + */ + + it('should retrieve the value for the left field when it is a form variable', () => { + let fakeForm = new FormModel({variables: [ + { name: 'FORM_VARIABLE_TEST', + type: 'string', + value: 'form_value_test' } + ]}); + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftRestResponseId = 'FORM_VARIABLE_TEST'; + + let leftValue = service.getLeftValue(fakeForm, visibilityObjTest); + + expect(leftValue).not.toBe(null); + expect(leftValue).toBe('form_value_test'); + }); + + it('should retrieve the value for the left field when it is a form value', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = 'FIELD_WITH_CONDITION'; + + let leftValue = service.getLeftValue(fakeFormWithField, visibilityObjTest); + + expect(leftValue).not.toBe(null); + expect(leftValue).toBe('field_with_condition_value'); + }); + + it('should retrieve left value from form values if it is present', () => { + formTest.values = formValues; + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = 'test_2'; + + let leftValue = service.getLeftValue(formTest, visibilityObjTest); + + expect(leftValue).not.toBe(null); + expect(leftValue).toBe('value_2'); + }); + + it('should return null for a value that is not on variable or form', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest = new WidgetVisibilityModel(); + + let leftValue = service.getLeftValue(fakeFormWithField, visibilityObjTest); + + expect(leftValue).toBe(null); + }); + + it('should evaluate the visibility for the field with single visibility condition between two field values', () => { + formTest.values = formValues; + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = 'test_1'; + visibilityObjTest.operator = '=='; + visibilityObjTest.rightFormFieldId = 'test_3'; + + let isVisible = service.evaluateVisibilityForField(formTest, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + + it('should evaluate true visibility for the field with single visibility condition between a field and a value', () => { + formTest.values = formValues; + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = 'test_1'; + visibilityObjTest.operator = '=='; + visibilityObjTest.rightValue = 'value_1'; + + let isVisible = service.evaluateVisibilityForField(formTest, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + + it('should evaluate the visibility for the field with single visibility condition between form values', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + + let isVisible = service.evaluateVisibilityForField(fakeFormWithField, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + + it('should evaluate the visibility for the field between form value and process var', (varReady) => { + service.getTaskProcessVariableModelsForTask(9999).subscribe( + (res: TaskProcessVariableModel[]) => { + varReady(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskProcessVariableModels) + }); + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightRestResponseId = 'TEST_VAR_2'; + + let isVisible = service.evaluateVisibilityForField(fakeFormWithField, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + + /* + it('should evaluate visibility with multiple conditions', (ready) => { + service.getTaskProcessVariableModelsForTask(9999).subscribe( + (res: TaskProcessVariableModel[]) => { + ready(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskProcessVariableModels) + }); + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest = new WidgetVisibilityModel(); + let chainedVisibilityObj = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightRestResponseId = 'TEST_VAR_2'; + visibilityObjTest.nextConditionOperator = 'and'; + chainedVisibilityObj.leftRestResponseId = 'TEST_VAR_2'; + chainedVisibilityObj.operator = '!empty'; + visibilityObjTest.nextCondition = chainedVisibilityObj; + + let isVisible = service.evaluateVisibilityForField(fakeFormWithField, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + */ + + it('should return true when the visibility condition is not valid', () => { + let visibilityObjTest = new WidgetVisibilityModel(); + visibilityObjTest.leftFormFieldId = ''; + visibilityObjTest.leftRestResponseId = ''; + visibilityObjTest.operator = '!='; + + let isVisible = service.getVisiblityForField(formTest, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + + it('should refresh the visibility for a form field object', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest = new WidgetVisibilityModel(); + fakeFormWithField.values = formValues; + visibilityObjTest.leftFormFieldId = 'test_1'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'test_3'; + let jsonFieldFake = {id: 'FAKE_FORM_FIELD_ID', value: 'FAKE_FORM_FIELD_VALUE', visibilityCondition: visibilityObjTest}; + let fakeFormField: FormFieldModel = new FormFieldModel(fakeFormWithField, jsonFieldFake); + + service.refreshVisibilityForField(fakeFormField); + + expect(fakeFormField.isVisible).toBeFalsy(); + }); + + it('should not change the isVisible if field does not have visibility condition', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + let jsonFieldFake = {id: 'FAKE_FORM_FIELD_ID', + value: 'FAKE_FORM_FIELD_VALUE', + visibilityCondition: null + }; + let fakeFormField: FormFieldModel = new FormFieldModel(fakeFormWithField, jsonFieldFake); + fakeFormField.isVisible = false; + service.refreshVisibilityForField(fakeFormField); + expect(fakeFormField.isVisible).toBeFalsy(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts new file mode 100644 index 0000000000..56249235b4 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts @@ -0,0 +1,207 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { Response, Http, Headers, RequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; +import { AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { FormModel, FormFieldModel } from '../components/widgets/core/index'; +import { WidgetVisibilityModel } from '../models/widget-visibility.model'; +import { TaskProcessVariableModel } from '../models/task-process-variable.model'; + +@Injectable() +export class WidgetVisibilityService { + + private processVarList: TaskProcessVariableModel[]; + + constructor(private http: Http, + private alfrescoSettingsService: AlfrescoSettingsService) { + } + + public updateVisibilityForForm(form: FormModel) { + if ( form && form.fields.length > 0 ) { + form.fields + .map( + contModel => + contModel.columns + .map( + contColModel => + contColModel + .fields.map( + field => + this.refreshVisibilityForField(field) ) + ) + ); + } + } + + public refreshVisibilityForField(field: FormFieldModel) { + if ( field.visibilityCondition ) { + field.isVisible = this.getVisiblityForField(field.form, field.visibilityCondition); + } + } + + public getVisiblityForField(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean { + let isLeftFieldPresent = visibilityObj.leftFormFieldId || visibilityObj.leftRestResponseId; + if ( !isLeftFieldPresent ) { + return true; + }else { + return this.evaluateVisibilityForField(form, visibilityObj); + } + } + + private evaluateVisibilityForField(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean { + let leftValue = this.getLeftValue(form, visibilityObj); + let rightValue = this.getRightValue(form, visibilityObj); + let actualResult = this.evaluateCondition(leftValue, rightValue, visibilityObj.operator); + if ( visibilityObj.nextCondition ) { + return this.evaluateLogicalOperation(visibilityObj.nextConditionOperator, + actualResult, + this.evaluateVisibilityForField( + form, visibilityObj.nextCondition) + ); + }else { + return actualResult; + } + } + + private getLeftValue(form: FormModel, visibilityObj: WidgetVisibilityModel) { + if ( visibilityObj.leftRestResponseId ) { + return this.getValueFromVariable(form, visibilityObj.leftRestResponseId); + } + return this.getValueOField(form, visibilityObj.leftFormFieldId); + } + + private getRightValue(form: FormModel, visibilityObj: WidgetVisibilityModel) { + let valueFound = null; + if ( visibilityObj.rightRestResponseId ) { + valueFound = this.getValueFromVariable(form, visibilityObj.rightRestResponseId); + }else if ( visibilityObj.rightFormFieldId ) { + valueFound = this.getValueOField(form, visibilityObj.rightFormFieldId); + }else { + valueFound = visibilityObj.rightValue; + } + return valueFound; + } + + private getValueOField(form: FormModel, field: string) { + let value = form.values[field] ? + form.values[field] : + this.getFormValueByName(form, field); + return value; + } + + private getFormValueByName(form: FormModel, name: string) { + for (let columns of form.json.fields) { + for ( let i in columns.fields ) { + if ( columns.fields.hasOwnProperty( i ) ) { + let res = columns.fields[i].find(field => field.id === name); + if ( res ) { + return res.value; + } + } + } + } + return null; + } + + private getValueFromVariable(form: FormModel, name: string) { + let formVariable = form.json.variables.find(formVar => formVar.name === name); + if ( !formVariable && this.processVarList ) { + formVariable = this.processVarList.find(variable => variable.id === name); + } + if ( formVariable ) { + return formVariable.value; + } + return null; + } + + private evaluateLogicalOperation(logicOp, previousValue, newValue): boolean { + switch ( logicOp ) { + case 'and': + return previousValue && newValue; + case 'or' : + return previousValue || newValue; + case 'and not': + return previousValue && !newValue; + case 'or not': + return previousValue || !newValue; + default: + console.error( 'NO valid operation!' ); + break; + } + } + + private evaluateCondition(leftValue, rightValue, operator): boolean { + switch ( operator ) { + case '==': + return leftValue + '' === rightValue; + case '<': + return leftValue < rightValue; + case '!=': + return leftValue + '' !== rightValue; + case '>': + return leftValue > rightValue; + case '>=': + return leftValue >= rightValue; + case '<=': + return leftValue <= rightValue; + case 'empty': + return leftValue ? leftValue === '' : true; + case '!empty': + return leftValue ? leftValue !== '' : false; + default: + console.error( 'NO valid operation!' ); + break; + } + return null; + } + + getTaskProcessVariableModelsForTask(taskId: string): Observable<TaskProcessVariableModel[]> { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/task-forms/${taskId}/variables`; + let options = this.getRequestOptions(); + return this.http + .get(url, options) + .map( (response: Response) => this.processVarList = <TaskProcessVariableModel[]> response.json()) + .catch(this.handleError); + } + + getTaskProcessVariableModelForTaskByName(taskId: string, processVarName: string): Observable<TaskProcessVariableModel> { + return this.getTaskProcessVariableModelsForTask(taskId) + .map( + (variables: TaskProcessVariableModel[]) => + variables.find(variable => variable.id === processVarName)); + } + + private getHeaders(): Headers { + return new Headers({ + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }); + } + + private getRequestOptions(): RequestOptions { + let headers = this.getHeaders(); + return new RequestOptions({headers: headers}); + } + + private handleError(error: Response) { + console.error(error); + return Observable.throw(error.json().error || 'Server error'); + } + +} diff --git a/ng2-components/ng2-activiti-form/tsconfig.json b/ng2-components/ng2-activiti-form/tsconfig.json new file mode 100644 index 0000000000..e14b002fcf --- /dev/null +++ b/ng2-components/ng2-activiti-form/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "removeComments": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "demo", + "dist", + "node_modules", + "typings/main", + "typings/main.d.ts", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-form/tslint.json b/ng2-components/ng2-activiti-form/tslint.json new file mode 100644 index 0000000000..0a34e57fec --- /dev/null +++ b/ng2-components/ng2-activiti-form/tslint.json @@ -0,0 +1,121 @@ +{ + "rules": { + "align": [ + true, + "parameters", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "interface-name": false, + "jsdoc-format": true, + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 180 + ], + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-any": false, + "no-arg": true, + "no-bitwise": false, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-constructor-vars": false, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unreachable": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "radix": true, + "semicolon": true, + "switch-default": true, + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef": false, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "use-strict": false, + "variable-name": [ + true, + "check-format", + "allow-leading-underscore", + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-operator", + "check-separator", + "check-type", + "check-module", + "check-decl" + ] + } +} diff --git a/ng2-components/ng2-activiti-form/typings.json b/ng2-components/ng2-activiti-form/typings.json new file mode 100644 index 0000000000..d8954c2485 --- /dev/null +++ b/ng2-components/ng2-activiti-form/typings.json @@ -0,0 +1,7 @@ +{ + "globalDependencies": { + "core-js": "registry:dt/core-js#0.0.0+20160317120654", + "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", + "node": "registry:dt/node#4.0.0+20160509154515" + } +} diff --git a/ng2-components/ng2-activiti-processlist/.editorconfig b/ng2-components/ng2-activiti-processlist/.editorconfig new file mode 100644 index 0000000000..75a2477db7 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/.editorconfig @@ -0,0 +1,23 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[package.json] +indent_style = space +indent_size = 2 + +[karma.conf.js] +indent_style = space +indent_size = 2 + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/ng2-components/ng2-activiti-processlist/.gitignore b/ng2-components/ng2-activiti-processlist/.gitignore new file mode 100644 index 0000000000..8987ee1a87 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/.gitignore @@ -0,0 +1,15 @@ +npm-debug.log +node_modules +jspm_packages +.idea +typings +coverage +src/**/*.js +src/**/*.js.map +src/**/*.d.ts +demo/**/*.js +demo/**/*.js.map +demo/**/*.d.ts +index.js +index.js.map +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-processlist/.travis.yml b/ng2-components/ng2-activiti-processlist/.travis.yml new file mode 100644 index 0000000000..ab817d7644 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/.travis.yml @@ -0,0 +1,12 @@ +language: node_js +node_js: + - v5 + - v4 + - '0.12' + - '0.10' +install: npm install +sudo: false +after_success: + npm run coverage +# Send coverage data to Coveralls +after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" diff --git a/ng2-components/ng2-activiti-processlist/LICENSE b/ng2-components/ng2-activiti-processlist/LICENSE new file mode 100644 index 0000000000..430d42bbea --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/ng2-components/ng2-activiti-processlist/README.md b/ng2-components/ng2-activiti-processlist/README.md new file mode 100644 index 0000000000..f4c967396b --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/README.md @@ -0,0 +1,101 @@ +# <ng-2-activiti-processlist> +[![NPM version][npm-image]][npm-url] +[![Build Status][travis-image]][travis-url] +[![Dependency Status][daviddm-image]][daviddm-url] +[![Coverage percentage][coveralls-image]][coveralls-url] +[![Style guide][style-image]][style-url] +[![Alfresco component][alfrescocomponent-image]][alfrescocomponent-url] + +## About ng-2-activiti-processlist +> Show available processes from the Activiti BPM suite + +## Installation + +```bash +npm install ng-2-activiti-processlist --save +``` + +## Example + +```html +<ng-2-activiti-processlist></ng-2-activiti-processlist> +``` + +## Reference + +Attribute | Options | Default | Description +--- | --- | --- | --- +`foo` | *string* | `bar` | Lorem ipsum dolor. + + +Method | Parameters | Returns | Description +--- | --- | --- | --- +`methodName()` | None. | void | Lorem ipsum dolor. + +## Develop command list + + +* To test your component + + ```sh + $ npm run test + ``` + +* To run the test in the browser + + ```sh + $ npm run test-browser + ``` + +* To run the test coverage + + ```sh + $ npm run coverage + ``` + +* To build the component + + ```sh + $ npm run build + ``` + +* To build the component and keep watching the changes + + ```sh + $ npm run build:w + ``` + +* To provide a live demo + + ```sh + $ cd demo + $ npm run start + ``` + +* To clean npm_modules and typings folder + + ```sh + $ npm run clean + ``` + +## History + +For detailed changelog, check [Releases](https://github.com/Alfresco/ng-2-activiti-processlist/releases). + +## Contributors + +[Contributors](https://github.com/Alfresco/ng-2-activiti-processlist/graphs/contributors) + + +[npm-image]: https://badge.fury.io/js/ng-2-activiti-processlist.svg +[npm-url]: https://npmjs.org/package/ng-2-activiti-processlist +[travis-image]: https://travis-ci.org/Alfresco/ng-2-activiti-processlist.svg?branch=master +[travis-url]: https://travis-ci.org/Alfresco/ng-2-activiti-processlist +[daviddm-image]: https://david-dm.org/Alfresco/ng-2-activiti-processlist.svg?theme=shields.io +[daviddm-url]: https://david-dm.org/Alfresco/ng-2-activiti-processlist +[coveralls-image]: https://coveralls.io/repos/Alfresco/ng-2-activiti-processlist/badge.svg +[coveralls-url]: https://coveralls.io/r/Alfresco/ng-2-activiti-processlist +[style-url]: https://github.com/mgechev/angular2-style-guide +[style-image]: https://mgechev.github.io/angular2-style-guide/images/badge.svg +[alfrescocomponent-image]: https://img.shields.io/badge/Alfresco%20component-approved-green.svg +[alfrescocomponent-url]: https://www.alfresco.com diff --git a/ng2-components/ng2-activiti-processlist/angular-cli.json b/ng2-components/ng2-activiti-processlist/angular-cli.json new file mode 100644 index 0000000000..c6a5e72386 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/angular-cli.json @@ -0,0 +1,27 @@ +{ + "project": { + "version": "ng2-activiti-processlist", + "name": "0.1.0" + }, + "apps": [ + { + "main": "src/ng2-activiti-processlist.component.ts", + "tsconfig": "tsconfig.json", + "mobile": false + } + ], + "addons": [], + "packages": [], + "test": { + "karma": { + "config": "karma.conf.js" + } + }, + "defaults": { + "prefix": "", + "sourceDir": "src", + "styleExt": "css", + "prefixInterfaces": false, + "lazyRoutePrefix": "+" + } +} diff --git a/ng2-components/ng2-activiti-processlist/assets/license_header.txt b/ng2-components/ng2-activiti-processlist/assets/license_header.txt new file mode 100644 index 0000000000..83fd1531a3 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/assets/license_header.txt @@ -0,0 +1,16 @@ +/*! + * @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. + */ \ No newline at end of file diff --git a/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts b/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts new file mode 100644 index 0000000000..b3e39360f4 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts @@ -0,0 +1,123 @@ +/*! + * @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 { DatePipe } from '@angular/common'; +import { + DataTableAdapter, ObjectDataTableAdapter, ObjectDataColumn, + DataRow, DataColumn, DataSorting +} from 'ng2-alfresco-datatable'; + +export class ProcessListDataTableAdapter extends ObjectDataTableAdapter implements DataTableAdapter { + + ERR_ROW_NOT_FOUND: string = 'Row not found'; + ERR_COL_NOT_FOUND: string = 'Column not found'; + + DEFAULT_DATE_FORMAT: string = 'medium'; + + private sorting: DataSorting; + private rows: DataRow[]; + private columns: DataColumn[]; + + constructor(rows: any, schema: DataColumn[]) { + super(rows, schema); + this.rows = rows; + this.columns = schema || []; + } + + getRows(): Array<DataRow> { + return this.rows; + } + + // TODO: disable this api + setRows(rows: Array<DataRow>) { + this.rows = rows || []; + this.sort(); + } + + getColumns(): Array<DataColumn> { + return this.columns; + } + + setColumns(columns: Array<DataColumn>) { + this.columns = columns || []; + } + + getValue(row: DataRow, col: DataColumn): any { + if (!row) { + throw new Error(this.ERR_ROW_NOT_FOUND); + } + if (!col) { + throw new Error(this.ERR_COL_NOT_FOUND); + } + let value = row.getValue(col.key); + + if (col.type === 'date') { + let datePipe = new DatePipe(); + let format = (<ActivitiDataColumn>(col)).format || this.DEFAULT_DATE_FORMAT; + try { + return datePipe.transform(value, format); + } catch (err) { + console.error(`Error parsing date ${value} to format ${format}`); + } + } + + return value; + } + + getSorting(): DataSorting { + return this.sorting; + } + + setSorting(sorting: DataSorting): void { + this.sorting = sorting; + + if (sorting && sorting.key && this.rows && this.rows.length > 0) { + this.rows.sort((a: DataRow, b: DataRow) => { + let left = a.getValue(sorting.key); + if (left) { + left = (left instanceof Date) ? left.valueOf().toString() : left.toString(); + } else { + left = ''; + } + + let right = b.getValue(sorting.key); + if (right) { + right = (right instanceof Date) ? right.valueOf().toString() : right.toString(); + } else { + right = ''; + } + + return sorting.direction === 'asc' + ? left.localeCompare(right) + : right.localeCompare(left); + }); + } + } + + sort(key?: string, direction?: string): void { + let sorting = this.sorting || new DataSorting(); + if (key) { + sorting.key = key; + sorting.direction = direction || 'asc'; + } + this.setSorting(sorting); + } +} + +export class ActivitiDataColumn extends ObjectDataColumn { + format: string; +} diff --git a/ng2-components/ng2-activiti-processlist/demo/.editorconfig b/ng2-components/ng2-activiti-processlist/demo/.editorconfig new file mode 100644 index 0000000000..8ed330c4a2 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/.editorconfig @@ -0,0 +1,10 @@ + +root = true + +[{src,scripts}/**.{ts,json,js}] +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/ng2-components/ng2-activiti-processlist/demo/.gitignore b/ng2-components/ng2-activiti-processlist/demo/.gitignore new file mode 100644 index 0000000000..6afdbb8367 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/.gitignore @@ -0,0 +1,6 @@ +typings/ +node_modules/ +.idea +dist/ +!systemjs.config.js +!browser-sync-config.js diff --git a/ng2-components/ng2-activiti-processlist/demo/README.md b/ng2-components/ng2-activiti-processlist/demo/README.md new file mode 100644 index 0000000000..2596f9176b --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/README.md @@ -0,0 +1,19 @@ +# ng2-activiti-processlist - Demo + +* To install dependencies + +```sh +$ npm install +``` + +* To provide a live demo + +```sh +$ npm run start +``` + +* To clean npm_modules and typings folder + +```sh +$ npm run clean +``` diff --git a/ng2-components/ng2-activiti-processlist/demo/index.html b/ng2-components/ng2-activiti-processlist/demo/index.html new file mode 100644 index 0000000000..63fd1b2b57 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/index.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>ng2-activiti-processlist Angular 2 + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + diff --git a/ng2-components/ng2-activiti-processlist/demo/package.json b/ng2-components/ng2-activiti-processlist/demo/package.json new file mode 100644 index 0000000000..fcc6e823e2 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/package.json @@ -0,0 +1,57 @@ +{ + "name": "ng2-activiti-processlist-demo", + "description": "Show available processes from the Activiti BPM suite - Demo", + "version": "0.1.0", + "author": "Will Abson", + "main": "index.js", + "scripts": { + "clean": "rimraf dist node_modules typings", + "typings": "typings install", + "postinstall": "npm run typings && npm run build", + "start": "npm run build && concurrently \"npm run tsc:w\" \"npm run server\" ", + "server": "wsrv -o -s -l", + "build": "npm run tslint && rimraf dist && npm run tsc", + "build:w": "npm run tslint && rimraf dist && npm run tsc:w", + "tsc": "tsc", + "tsc:w": "tsc -w", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts" + }, + "keywords": [ + "ng2", + "angular", + "angular2", + "alfresco", + "demo" + ], + "dependencies": { + "@angular/common": "2.0.0-rc.3", + "@angular/compiler": "2.0.0-rc.3", + "@angular/core": "2.0.0-rc.3", + "@angular/http": "2.0.0-rc.3", + "@angular/platform-browser": "2.0.0-rc.3", + "@angular/platform-browser-dynamic": "2.0.0-rc.3", + "@angular/router": "3.0.0-alpha.7", + "@angular/router-deprecated": "2.0.0-rc.2", + "@angular/upgrade": "2.0.0-rc.3", + "alfresco-js-api": "^0.3.0", + "systemjs": "0.19.27", + "core-js": "^2.4.0", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.6", + "zone.js": "^0.6.12", + "ng2-activiti-processlist": "file:../", + "material-design-icons": "^2.2.3", + "material-design-lite": "^1.1.3" + }, + "devDependencies": { + "concurrently": "^2.0.0", + "rimraf": "2.5.2", + "tslint": "^3.8.1", + "typescript": "^1.8.10", + "typings": "^1.0.4", + "wsrv": "0.1.3" + }, + "publishConfig": { + "registry": "http://devproducts.alfresco.me:4873/" + } +} diff --git a/ng2-components/ng2-activiti-processlist/demo/src/main.ts b/ng2-components/ng2-activiti-processlist/demo/src/main.ts new file mode 100644 index 0000000000..9d3c4df947 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/src/main.ts @@ -0,0 +1,101 @@ +/*! + * @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, OnInit, Injectable, provide } from '@angular/core'; +import { bootstrap } from '@angular/platform-browser-dynamic'; +import { + ACTIVITI_PROCESSLIST_PROVIDERS, + ACTIVITI_PROCESSLIST_DIRECTIVES +} from 'ng2-activiti-processlist/dist/ng2-activiti-processlist'; +import { + AlfrescoAuthenticationService, + AlfrescoSettingsService, + ALFRESCO_CORE_PROVIDERS +} from 'ng2-alfresco-core'; + +@Component({ + selector: 'my-app', + template: `label for="token">Insert a valid access token / ticket:
    +
    +
    +

    +
    + Authentication failed to ip {{ bpmHost }} with user: admin, admin, you can still try to add a valid token to perform + operations. +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    `, + providers: [ACTIVITI_PROCESSLIST_PROVIDERS], + directives: [ACTIVITI_PROCESSLIST_DIRECTIVES] +}) +class MyDemoApp implements OnInit { + + authenticated: boolean; + ecmHost: string = 'http://127.0.0.1:9999'; + token: string; + + constructor( + private authService: AlfrescoAuthenticationService, + private settingsService: AlfrescoSettingsService + ) { + console.log('constructor'); + + settingsService.setProviders('BPM'); + settingsService.bpmHost = this.bpmHost; + + if (this.authService.getTicket()) { + this.token = this.authService.getTicket(); + } + } + + ngOnInit() { + this.login(); + } + + public updateToken(): void { + localStorage.setItem('token', this.token); + } + + public updateHost(): void { + this.settingsService.ecmHost = this.ecmHost; + this.login(); + } + + login() { + this.authService.login('admin', 'admin').subscribe( + token => { + console.log(token); + this.token = token; + this.authenticated = true; + }, + error => { + console.log(error); + this.authenticated = false; + }); + } +} + +bootstrap(MyDemoApp, [ + ALFRESCO_CORE_PROVIDERS +]); diff --git a/ng2-components/ng2-activiti-processlist/demo/systemjs.config.js b/ng2-components/ng2-activiti-processlist/demo/systemjs.config.js new file mode 100644 index 0000000000..a09961cab3 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/systemjs.config.js @@ -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. + */ + +(function (global) { + + // map tells the System loader where to look for things + var map = { + 'app': 'dist', + '@angular': 'node_modules/@angular', + 'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api', + 'rxjs': 'node_modules/rxjs', + 'ng2-translate': 'node_modules/ng2-translate', + 'ng2-activiti-processlist': 'node_modules/ng2-activiti-processlist', + 'ng2-alfresco-core': 'node_modules/ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'node_modules/ng2-alfresco-datatable/dist' + }; + + // packages tells the System loader how to load when no filename and/or no extension + var packages = { + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-activiti-processlist': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' } + }; + + var ngPackageNames = [ + 'common', + 'compiler', + 'core', + 'http', + 'platform-browser', + 'platform-browser-dynamic', + 'router', + 'router-deprecated', + 'upgrade' + ]; + // Individual files (~300 requests): + function packIndex(pkgName) { + packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' }; + } + // Bundled (~40 requests): + function packUmd(pkgName) { + packages['@angular/'+pkgName] = { main: '/bundles/'+ pkgName + '.umd.js', defaultExtension: 'js' }; + } + // Most environments should use UMD; some (Karma) need the individual index files + var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; + // Add package entries for angular packages + ngPackageNames.forEach(setPackageConfig); + var config = { + map: map, + packages: packages + }; + System.config(config); +})(this); diff --git a/ng2-components/ng2-activiti-processlist/demo/tsconfig.json b/ng2-components/ng2-activiti-processlist/demo/tsconfig.json new file mode 100644 index 0000000000..772c3a7e75 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "removeComments": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "typings/main", + "typings/main.d.ts" + ] +} diff --git a/ng2-components/ng2-activiti-processlist/demo/tslint.json b/ng2-components/ng2-activiti-processlist/demo/tslint.json new file mode 100644 index 0000000000..8c48e76469 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/tslint.json @@ -0,0 +1,124 @@ +{ + "rules": { + "align": [ + true, + "parameters", + "arguments", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space", + "check-lowercase" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "interface-name": false, + "jsdoc-format": true, + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 140 + ], + "member-ordering": [ + true, + "public-before-private", + "static-before-instance", + "variables-before-functions" + ], + "no-any": false, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-constructor-vars": false, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unreachable": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "radix": true, + "semicolon": true, + "switch-default": true, + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef": false, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "use-strict": false, + "variable-name": [ + true, + "check-format", + "allow-leading-underscore", + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-operator", + "check-separator", + "check-type", + "check-module", + "check-decl" + ] + } +} diff --git a/ng2-components/ng2-activiti-processlist/demo/typings.json b/ng2-components/ng2-activiti-processlist/demo/typings.json new file mode 100644 index 0000000000..7e0e18568d --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/demo/typings.json @@ -0,0 +1,7 @@ +{ + "globalDependencies": { + "core-js": "registry:dt/core-js#0.0.0+20160317120654", + "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", + "node": "registry:dt/node#4.0.0+20160509154515" + } +} diff --git a/ng2-components/ng2-activiti-processlist/index.ts b/ng2-components/ng2-activiti-processlist/index.ts new file mode 100644 index 0000000000..34bd934992 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/index.ts @@ -0,0 +1,46 @@ +/*! + * @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 { ActivitiProcessInstanceListComponent } from './src/components/activiti-processlist.component'; +import { ActivitiProcessFilters } from './src/components/activiti-filters.component'; +import { ActivitiProcessInstanceHeader } from './src/components/activiti-process-instance-header.component'; +import { ActivitiProcessInstanceTasks } from './src/components/activiti-process-instance-tasks.component'; +import { ActivitiComments } from './src/components/activiti-comments.component'; +import { ActivitiProcessInstanceDetails } from './src/components/activiti-process-instance-details.component'; +import { ActivitiStartProcessButton } from './src/components/activiti-start-process.component'; +import { ActivitiProcessService } from './src/services/activiti-process.service'; + +// components +export * from './src/components/activiti-processlist.component'; +export * from './src/components/activiti-process-instance-details.component'; + +// services +export * from './src/services/activiti-process.service'; + +export const ACTIVITI_PROCESSLIST_DIRECTIVES: [any] = [ + ActivitiProcessInstanceListComponent, + ActivitiProcessFilters, + ActivitiProcessInstanceDetails, + ActivitiProcessInstanceHeader, + ActivitiProcessInstanceTasks, + ActivitiComments, + ActivitiStartProcessButton +]; + +export const ACTIVITI_PROCESSLIST_PROVIDERS: [any] = [ + ActivitiProcessService +]; diff --git a/ng2-components/ng2-activiti-processlist/karma-test-shim.js b/ng2-components/ng2-activiti-processlist/karma-test-shim.js new file mode 100644 index 0000000000..8c7ba100a2 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/karma-test-shim.js @@ -0,0 +1,108 @@ +// Tun on full stack traces in errors to help debugging +Error.stackTraceLimit = Infinity; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +__karma__.loaded = function() {}; + +var map = { + 'app': 'base/dist', + 'rxjs': 'base/node_modules/rxjs', + '@angular': 'base/node_modules/@angular', + 'ng2-alfresco-core': '/base/node_modules/ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': '/base/node_modules/ng2-alfresco-datatable/dist', + 'ng2-translate' : '/base/node_modules/ng2-translate' +}; + +var packages = { + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-translate': { defaultExtension: 'js' } +}; + +var packageNames = [ + '@angular/common', + '@angular/compiler', + '@angular/core', + '@angular/http', + '@angular/platform-browser', + '@angular/platform-browser-dynamic', + '@angular/router', + '@angular/router-deprecated', + '@angular/testing', + '@angular/upgrade' +]; + +packageNames.forEach(function(pkgName) { + packages[pkgName] = { main: 'index.js', defaultExtension: 'js' }; +}); + +packages['base/dist'] = { + defaultExtension: 'js', + format: 'register', + map: Object.keys(window.__karma__.files).filter(onlyAppFiles).reduce(createPathRecords, {}) +}; + +var config = { + map: map, + packages: packages +}; + +System.config(config); + +System.import('@angular/platform-browser/src/browser/browser_adapter') + .then(function(browser_adapter) { browser_adapter.BrowserDomAdapter.makeCurrent(); }) + .then(function () { + return Promise.all([ + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') + ]) + }) + .then(function (providers) { + var testing = providers[0]; + var testingBrowser = providers[1]; + + testing.setBaseTestProviders( + testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, + testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS); + }) + .then(function() { return Promise.all(resolveTestFiles()); }) + .then( + function() { + __karma__.start(); + }, + function(error) { + if(typeof __karma__.error == 'function') { + __karma__.error(error.stack || error); + }else{ + console.error(error); + } + } + ); +function createPathRecords(pathsMapping, appPath) { + var pathParts = appPath.split('/'); + var moduleName = './' + pathParts.slice(Math.max(pathParts.length - 2, 1)).join('/'); + moduleName = moduleName.replace(/\.js$/, ''); + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath]; + return pathsMapping; +} + +function onlyAppFiles(filePath) { + return /\/base\/dist\/(?!.*\.spec\.js$).*\.js$/.test(filePath); +} + +function onlySpecFiles(path) { + return /\.spec\.js$/.test(path); +} + +function resolveTestFiles() { + return Object.keys(window.__karma__.files) // All files served by Karma. + .filter(onlySpecFiles) + .map(function(moduleName) { + // loads all spec files via their global module names (e.g. + // 'base/dist/vg-player/vg-player.spec') + return System.import(moduleName); + }); +} diff --git a/ng2-components/ng2-activiti-processlist/karma.conf.js b/ng2-components/ng2-activiti-processlist/karma.conf.js new file mode 100644 index 0000000000..1a3d76b9db --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/karma.conf.js @@ -0,0 +1,93 @@ +'use strict'; + +module.exports = function (config) { + config.set({ + + basePath: '.', + + frameworks: ['jasmine'], + + files: [ + // paths loaded by Karma + {pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: true}, + {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: false}, + {pattern: 'node_modules/zone.js/dist/zone.js', included: true, watched: true}, + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.map', included: false, watched: false}, + {pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.html', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.css', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.html', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.css', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', included: true, watched: false}, + + {pattern: 'karma-test-shim.js', included: true, watched: true}, + + // paths loaded via module imports + {pattern: 'dist/**/*.js', included: false, watched: true}, + {pattern: 'dist/**/*.html', included: true, served: true, watched: true}, + {pattern: 'dist/**/*.css', included: true, served: true, watched: true}, + + // paths to support debugging with source maps in dev tools + {pattern: 'src/**/*.ts', included: false, watched: false}, + {pattern: 'dist/**/*.js.map', included: false, watched: false} + ], + + // proxied base paths + proxies: { + // required for component assets fetched by Angular's compiler + '/src/': '/base/src/' + }, + + // list of files to exclude + exclude: [ + 'node_modules/**/*spec.js' + ], + + port: 9876, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + colors: true, + + autoWatch: true, + + browsers: ['Chrome'], + + // Karma plugins loaded + plugins: [ + 'karma-jasmine', + 'karma-coverage', + 'karma-chrome-launcher', + 'karma-mocha-reporter', + 'karma-jasmine-html-reporter' + ], + + // Coverage reporter generates the coverage + reporters: ['mocha', 'coverage', 'coveralls', 'kjhtml'], + + // Source files that you wanna generate coverage for. + // Do not include tests or libraries (these files will be instrumented by Istanbul) + preprocessors: { + 'dist/**/!(*spec).js': ['coverage'] + }, + + coverageReporter: { + dir: 'coverage/', + subdir: 'report', + reporters: [ + {type: 'text'}, + {type: 'json', file: 'coverage-final.json'}, + {type: 'html'}, + {type: 'lcov'} + ] + } + }) +}; diff --git a/ng2-components/ng2-activiti-processlist/package.json b/ng2-components/ng2-activiti-processlist/package.json new file mode 100644 index 0000000000..f918590830 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/package.json @@ -0,0 +1,109 @@ +{ + "name": "ng2-activiti-processlist", + "description": "Show active processes from the Activiti BPM suite", + "version": "0.3.0", + "author": "Alfresco Software, Ltd.", + "scripts": { + "postinstall": "typings install", + "clean": "rimraf dist node_modules typings", + "typings": "typings install", + "server": "wsrv -o -p 9875", + "build": "npm run tslint && rimraf dist && tsc && npm run copy-dist && license-check", + "build:w": "npm run tslint && rimraf dist && npm run watch-task", + "watch-task": "concurrently \"npm run tsc:w\" \"npm run copy-dist:w\" \"license-check\"", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'src/{,**/}**.ts'", + "copy-dist": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src", + "copy-dist:w": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src -w", + "tsc": "tsc", + "tsc:w": "tsc -w", + "pretest": "npm run build", + "test": "karma start karma.conf.js --reporters mocha,coverage --single-run", + "test-browser": "npm run build && concurrently \"karma start karma.conf.js --reporters kjhtml\" \"npm run watch-task\"", + "posttest": "node_modules/.bin/remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html", + "coverage": "npm run test && http-server -c-1 -o -p 9875 ./coverage/report", + "prepublish": "npm run build", + "travis": "npm link ng2-alfresco-core ng2-alfresco-datatable" + }, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/Alfresco/alfresco-ng2-components.git" + }, + "bugs": { + "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Will Abson", + "email": "will.abson@alfresco.com" + } + ], + "keywords": [ + "ng2", + "angular", + "angular2", + "alfresco" + ], + "dependencies": { + "@angular/common": "2.0.0-rc.3", + "@angular/compiler": "2.0.0-rc.3", + "@angular/core": "2.0.0-rc.3", + "@angular/http": "2.0.0-rc.3", + "@angular/platform-browser": "2.0.0-rc.3", + "@angular/platform-browser-dynamic": "2.0.0-rc.3", + "@angular/router": "3.0.0-alpha.7", + "@angular/router-deprecated": "2.0.0-rc.2", + "@angular/upgrade": "2.0.0-rc.3", + "alfresco-js-api": "^0.3.0", + "systemjs": "0.19.27", + "core-js": "^2.4.0", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.6", + "zone.js": "^0.6.12", + "ng2-translate": "2.2.2", + "ng2-alfresco-core": "0.3.0", + "ng2-alfresco-datatable": "0.3.0", + "ng2-activiti-tasklist": "0.3.0" + }, + "devDependencies": { + "angular-cli": "1.0.0-beta.9", + "concurrently": "^2.1.0", + "coveralls": "^2.11.9", + "cpx": "^1.3.1", + "http-server": "0.8.5", + "jasmine-ajax": "^3.2.0", + "jasmine-core": "2.4.1", + "karma": "~0.13.22", + "karma-chrome-launcher": "~1.0.1", + "karma-coverage": "^1.0.0", + "karma-coveralls": "^1.1.2", + "karma-jasmine": "~1.0.2", + "karma-jasmine-ajax": "^0.1.13", + "karma-jasmine-html-reporter": "^0.2.0", + "karma-mocha-reporter": "^2.0.3", + "license-check": "^1.0.4", + "remap-istanbul": "^0.6.3", + "rimraf": "2.5.2", + "traceur": "^0.0.91", + "tslint": "^3.8.1", + "typescript": "^1.8.10", + "typings": "^1.0.4" + }, + "license-check-config": { + "src": [ + "**/*.js", + "**/*.ts", + "!/**/coverage/**/*", + "!/**/demo/**/*", + "!/**/node_modules/**/*", + "!/**/typings/**/*", + "!*.js" + ], + "path": "assets/license_header.txt", + "blocking": false, + "logInfo": false, + "logError": true + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.model.mock.ts b/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.model.mock.ts new file mode 100644 index 0000000000..6eba34a88e --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.model.mock.ts @@ -0,0 +1,39 @@ +/*! + * @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 { ProcessInstance } from '../models/process-instance'; + +export class ProcessList { + + data: ProcessInstance[]; + size: number; + start: number; + total: number; + + constructor(data?: ProcessInstance[]) { + this.data = data || []; + } +} + +export class SingleProcessList extends ProcessList { + constructor(name?: string) { + let instance = new ProcessInstance(); + instance.id = '123'; + instance.name = name; + super([instance]); + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/assets/translation.service.mock.ts b/ng2-components/ng2-activiti-processlist/src/assets/translation.service.mock.ts new file mode 100644 index 0000000000..023104944a --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/assets/translation.service.mock.ts @@ -0,0 +1,37 @@ +/*! + * @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 { Observable } from 'rxjs/Rx'; +import { EventEmitter } from '@angular/core'; + +export interface LangChangeEvent { + lang: string; + translations: any; +} + +export class TranslationMock { + + public onLangChange: EventEmitter = new EventEmitter(); + + public get(key: string|Array, interpolateParams?: Object): Observable { + return Observable.of(key); + } + + addTranslationFolder() { + + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.css b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.css new file mode 100644 index 0000000000..ff0f8dd865 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.css @@ -0,0 +1,7 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.html new file mode 100644 index 0000000000..169719612e --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.html @@ -0,0 +1,35 @@ +{{ 'DETAILS.LABELS.COMMENTS' |translate }} +
    add
    +
    + Add a comment +
    + + +
    + {{ 'DETAILS.COMMENTS.NONE' | translate }} +
    + + + +

    New comment

    +
    +
    + + +
    +
    +
    + + +
    +
    diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts new file mode 100644 index 0000000000..95f1219c92 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts @@ -0,0 +1,121 @@ +/*! + * @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, Input, OnInit, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiProcessService } from './../services/activiti-process.service'; +import { Comment } from '../models/comment.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-comments', + moduleId: __moduleName, + templateUrl: './activiti-comments.component.html', + styleUrls: ['./activiti-comments.component.css'], + providers: [ActivitiProcessService], + pipes: [ AlfrescoPipeTranslate ] + +}) +export class ActivitiComments implements OnInit { + + @Input() + processId: string; + + @ViewChild('dialog') + dialog: any; + + comments: Comment [] = []; + + private commentObserver: Observer; + comment$: Observable; + + message: string; + + /** + * Constructor + * @param auth + * @param translate + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiProcess: ActivitiProcessService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src'); + } + + this.comment$ = new Observable(observer => this.commentObserver = observer).share(); + + } + + ngOnInit() { + this.comment$.subscribe((comment: Comment) => { + this.comments.push(comment); + }); + + if (this.processId) { + this.load(this.processId); + } + } + + public load(taskId: string) { + this.comments = []; + if (this.processId) { + this.activitiProcess.getProcessInstanceComments(this.processId).subscribe( + (res: Comment[]) => { + res.forEach((comment) => { + this.commentObserver.next(comment); + }); + }, + (err) => { + console.log(err); + } + ); + } else { + this.comments = []; + } + } + + public showDialog() { + if (this.dialog) { + this.dialog.nativeElement.showModal(); + } + } + + public add() { + this.activitiProcess.addProcessInstanceComment(this.processId, this.message).subscribe( + (res: Comment) => { + this.comments.push(res); + this.message = ''; + }, + (err) => { + console.log(err); + } + ); + this.cancel(); + } + + public cancel() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.css b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.css new file mode 100644 index 0000000000..6b7e0a7a66 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.css @@ -0,0 +1,3 @@ +.mdl-list__item { + cursor: pointer; +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.html new file mode 100644 index 0000000000..580798c6a3 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.ts new file mode 100644 index 0000000000..9032d2f173 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.ts @@ -0,0 +1,131 @@ +/*! + * @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, Output, EventEmitter, OnInit, Input } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiProcessService } from './../services/activiti-process.service'; +import { FilterModel } from '../models/filter.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-process-filters', + moduleId: __moduleName, + templateUrl: './activiti-filters.component.html', + styleUrls: ['activiti-filters.component.css'], + providers: [ActivitiProcessService], + pipes: [AlfrescoPipeTranslate] + +}) +export class ActivitiProcessFilters implements OnInit { + + @Output() + filterClick: EventEmitter = new EventEmitter(); + + @Output() + onSuccess: EventEmitter = new EventEmitter(); + + @Output() + onError: EventEmitter = new EventEmitter(); + + @Input() + appId: string; + + @Input() + appName: string; + + private filterObserver: Observer; + filter$: Observable; + + currentFilter: FilterModel; + + filters: FilterModel [] = []; + + /** + * Constructor + * @param auth + * @param translate + * @param activiti + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + public activiti: ActivitiProcessService) { + this.filter$ = new Observable(observer => this.filterObserver = observer).share(); + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src'); + } + } + + ngOnInit() { + this.filter$.subscribe((filter: FilterModel) => { + this.filters.push(filter); + }); + + this.load(); + } + + /** + * The method call the adapter data table component for render the task list + * @param tasks + */ + private load() { + if (this.appName) { + this.filterByAppName(); + } else { + this.filterByAppId(this.appId); + } + } + + private filterByAppId(appId) { + this.activiti.getProcessFilters(appId).subscribe( + (res: FilterModel[]) => { + res.forEach((filter) => { + this.filterObserver.next(filter); + }); + this.onSuccess.emit(res); + }, + (err) => { + console.log(err); + this.onError.emit(err); + } + ); + } + + private filterByAppName() { + this.activiti.getDeployedApplications(this.appName).subscribe( + application => { + this.filterByAppId(application.id); + }, + (err) => { + console.log(err); + this.onError.emit(err); + }); + } + + /** + * Pass the selected filter as next + * @param filter + */ + public selectFilter(filter: FilterModel) { + this.currentFilter = filter; + this.filterClick.emit(filter); + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.css b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.css new file mode 100644 index 0000000000..07eaf92d80 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.css @@ -0,0 +1,3 @@ +:host { + width: 100%; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.html new file mode 100644 index 0000000000..741dcabc47 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.html @@ -0,0 +1,13 @@ +
    {{ 'DETAILS.MESSAGES.NONE'|translate }}
    +
    +

    {{processInstanceDetails.name}}

    + +
    +
    + +
    +
    + +
    +
    +
    diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.ts new file mode 100644 index 0000000000..0196992b82 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.ts @@ -0,0 +1,108 @@ +/*! + * @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, Input, ViewChild, Output, EventEmitter } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiProcessService } from './../services/activiti-process.service'; +import { ActivitiProcessInstanceHeader } from './activiti-process-instance-header.component'; +import { ActivitiProcessInstanceTasks } from './activiti-process-instance-tasks.component'; +import { ActivitiComments } from './activiti-comments.component'; +import { ProcessInstance } from '../models/process-instance'; + + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-process-instance-details', + moduleId: __moduleName, + templateUrl: './activiti-process-instance-details.component.html', + styleUrls: ['./activiti-process-instance-details.component.css'], + providers: [ActivitiProcessService], + directives: [ActivitiProcessInstanceHeader, ActivitiComments, ActivitiProcessInstanceTasks], + pipes: [AlfrescoPipeTranslate] + +}) +export class ActivitiProcessInstanceDetails { + + @Input() + processInstanceId: string; + + @ViewChild('activitiprocessheader') + processInstanceHeader: ActivitiProcessInstanceHeader; + + @ViewChild('activitiprocesstasks') + tasksList: ActivitiProcessInstanceTasks; + + @ViewChild('activitiprocesscomments') + commentsList: ActivitiComments; + + @Input() + showTitle: boolean = true; + + @Input() + showRefreshButton: boolean = true; + + @Output() + processCancelledEmitter = new EventEmitter(); + + @Output() + taskFormCompletedEmitter = new EventEmitter(); + + processInstanceDetails: ProcessInstance; + + /** + * Constructor + * @param auth + * @param translate + * @param activitiProcess + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiProcess: ActivitiProcessService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src'); + } + } + + load(processId: string) { + if (processId) { + this.activitiProcess.getProcess(processId).subscribe( + (res: ProcessInstance) => { + this.processInstanceDetails = res; + if (this.processInstanceDetails) { + if (this.commentsList) { + this.commentsList.load(this.processInstanceDetails.id); + } + if (this.tasksList) { + this.tasksList.load(this.processInstanceDetails.id); + } + } + } + ); + } + } + + processCancelled(data: any) { + this.processCancelledEmitter.emit(data); + } + + taskFormCompleted(data: any) { + this.taskFormCompletedEmitter.emit(data); + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.css b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.css new file mode 100644 index 0000000000..20fbab0626 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.css @@ -0,0 +1,7 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.html new file mode 100644 index 0000000000..e48011f9bc --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.html @@ -0,0 +1,15 @@ +
    +
    +
    + {{ 'DETAILS.LABELS.STARTED_BY' | translate }}: + {{getStartedByFullName()}} +
    +
    + {{ 'DETAILS.LABELS.STARTED' | translate }}: + {{getStartedDate() | date:'medium'}} +
    +
    + +
    +
    +
    diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.ts new file mode 100644 index 0000000000..9771c3624e --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.ts @@ -0,0 +1,73 @@ +/*! + * @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, Input, Output, EventEmitter } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ProcessInstance } from '../models/process-instance'; +import { ActivitiProcessService } from './../services/activiti-process.service'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-process-instance-header', + moduleId: __moduleName, + templateUrl: './activiti-process-instance-header.component.html', + styleUrls: ['./activiti-process-instance-header.component.css'], + pipes: [ AlfrescoPipeTranslate ] + +}) +export class ActivitiProcessInstanceHeader { + + @Input() + processInstance: ProcessInstance; + + @Output() + processCancelled = new EventEmitter(); + + /** + * Constructor + * @param auth + * @param translate + * @param activitiProcess + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiProcess: ActivitiProcessService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + } + + getStartedByFullName() { + if (this.processInstance && this.processInstance.startedBy) { + return (this.processInstance.startedBy.firstName && this.processInstance.startedBy.firstName !== 'null' + ? this.processInstance.startedBy.firstName + ' ' : '') + + this.processInstance.startedBy.lastName; + } + return ''; + } + + getStartedDate() { + return this.processInstance ? new Date(this.processInstance.started) : null; + } + + cancelProcess() { + this.processCancelled.emit(this.activitiProcess.cancelProcess(this.processInstance.id)); + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.css b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.css new file mode 100644 index 0000000000..ca7a24db2c --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.css @@ -0,0 +1,19 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} + +.material-icons:hover { + color: rgb(255, 152, 0); +} + +.task-details-dialog { + width: 600px; +} + +.process-tasks-refresh { + float: right; +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.html new file mode 100644 index 0000000000..0e4e014aa9 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.html @@ -0,0 +1,52 @@ +
    + +
    + +{{ 'DETAILS.LABELS.TASKS_ACTIVE'|translate }} + + +
    + {{ 'DETAILS.TASKS.NO_ACTIVE' | translate }} +
    + +{{ 'DETAILS.LABELS.TASKS_COMPLETED'|translate }} + + +
    + {{ 'DETAILS.TASKS.NO_COMPLETED' | translate }} +
    + + +

    {{ 'DETAILS.TASKS.TASK_DETAILS' | translate }}

    +
    + +
    +
    + +
    +
    diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.ts new file mode 100644 index 0000000000..5b2cfae098 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.ts @@ -0,0 +1,182 @@ +/*! + * @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, Input, OnInit, ViewChild, Output, EventEmitter } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiProcessService } from './../services/activiti-process.service'; +import { TaskDetailsModel } from '../models/task-details.model'; +import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-process-instance-tasks', + moduleId: __moduleName, + templateUrl: './activiti-process-instance-tasks.component.html', + styleUrls: ['./activiti-process-instance-tasks.component.css'], + providers: [ActivitiProcessService], + directives: [ ALFRESCO_TASKLIST_DIRECTIVES ], + pipes: [ AlfrescoPipeTranslate ] + +}) +export class ActivitiProcessInstanceTasks implements OnInit { + + @Input() + processId: string; + + @Input() + showRefreshButton: boolean = true; + + @Output() + taskFormCompletedEmitter = new EventEmitter(); + + activeTasks: TaskDetailsModel[] = []; + completedTasks: TaskDetailsModel[] = []; + + private taskObserver: Observer; + private completedTaskObserver: Observer; + + task$: Observable; + completedTask$: Observable; + + message: string; + + selectedTaskId: string; + + @ViewChild('dialog') + dialog: any; + + @ViewChild('taskdetails') + taskdetails: any; + + /** + * Constructor + * @param auth + * @param translate + * @param activitiProcess + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiProcess: ActivitiProcessService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src'); + } + + this.task$ = new Observable(observer => this.taskObserver = observer).share(); + this.completedTask$ = new Observable(observer => this.completedTaskObserver = observer).share(); + + } + + ngOnInit() { + this.task$.subscribe((task: TaskDetailsModel) => { + this.activeTasks.push(task); + }); + this.completedTask$.subscribe((task: TaskDetailsModel) => { + this.completedTasks.push(task); + }); + + if (this.processId) { + this.load(this.processId); + } + } + + public load(processId: string) { + this.loadActive(processId); + this.loadCompleted(processId); + } + + public loadActive(processId: string) { + this.activeTasks = []; + if (processId) { + this.activitiProcess.getProcessTasks(processId, null).subscribe( + (res: TaskDetailsModel[]) => { + res.forEach((task) => { + this.taskObserver.next(task); + }); + }, + (err) => { + console.log(err); + } + ); + } else { + this.activeTasks = []; + } + } + + public loadCompleted(processId: string) { + this.completedTasks = []; + if (processId) { + this.activitiProcess.getProcessTasks(processId, 'completed').subscribe( + (res: TaskDetailsModel[]) => { + res.forEach((task) => { + this.completedTaskObserver.next(task); + }); + }, + (err) => { + console.log(err); + } + ); + } else { + this.completedTasks = []; + } + } + + getUserFullName(user: any) { + if (user) { + return (user.firstName && user.firstName !== 'null' + ? user.firstName + ' ' : '') + + user.lastName; + } + return ''; + } + + public clickTask($event: any, task: TaskDetailsModel) { + this.selectedTaskId = task.id; + this.taskdetails.loadDetails(task.id); + this.showDialog(); + } + + public showDialog() { + if (this.dialog) { + this.dialog.nativeElement.showModal(); + } + } + + public cancelDialog() { + this.closeDialog(); + } + + private closeDialog() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } + + public taskFormCompleted() { + this.closeDialog(); + this.load(this.processId); + this.taskFormCompletedEmitter.emit(this.processId); + } + + public onRefreshClicked() { + this.load(this.processId); + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.html new file mode 100644 index 0000000000..81e5da9857 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.html @@ -0,0 +1,12 @@ +
    {{ 'FILTERS.MESSAGES.NONE' | translate }}
    +
    +
    + + +
    +
    + {{ 'PROCESSLIST.NONE' | translate }} +
    +
    diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts new file mode 100644 index 0000000000..6f22951373 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts @@ -0,0 +1,147 @@ +/*! + * @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, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { AlfrescoPipeTranslate, AlfrescoTranslationService, CONTEXT_MENU_DIRECTIVES, CONTEXT_MENU_PROVIDERS } from 'ng2-alfresco-core'; +import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter, DataRowEvent } from 'ng2-alfresco-datatable'; +import { ActivitiProcessService } from '../services/activiti-process.service'; +import { FilterModel } from '../models/filter.model'; + +declare let __moduleName: string; + +@Component({ + moduleId: __moduleName, + selector: 'activiti-process-instance-list', + styles: [ + ` + :host h1 { + font-size:22px + } + ` + ], + templateUrl: './activiti-processlist.component.html', + directives: [ ALFRESCO_DATATABLE_DIRECTIVES, CONTEXT_MENU_DIRECTIVES ], + pipes: [ AlfrescoPipeTranslate ], + providers: [ CONTEXT_MENU_PROVIDERS, ActivitiProcessService ] +}) +export class ActivitiProcessInstanceListComponent implements OnInit { + + errorMessage: string; + data: ObjectDataTableAdapter; + currentProcessInstanceId: string; + + @Input() + filter: FilterModel; + + @Input() + schemaColumn: any[] = [ + {type: 'text', key: 'id', title: 'Id', sortable: true}, + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'started', title: 'Started', sortable: true}, + {type: 'text', key: 'startedBy.email', title: 'Started By', sortable: true} + ]; + + @Output() + rowClick: EventEmitter = new EventEmitter(); + + @Output() + onSuccess: EventEmitter = new EventEmitter(); + + @Output() + onError: EventEmitter = new EventEmitter(); + + constructor (private processService: ActivitiProcessService, private translate: AlfrescoTranslationService) { + if (translate !== null) { + translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src'); + } + } + + ngOnInit() { + this.data = new ObjectDataTableAdapter( + [], + this.schemaColumn + ); + if (this.filter) { + this.load(this.filter); + } + } + + load(filter: FilterModel) { + this.processService.getProcessInstances(filter) + .subscribe( + (processInstances) => { + this.renderProcessInstances(processInstances); + this.onSuccess.emit(processInstances); + }, + error => { + this.errorMessage = error; + this.onError.emit(error); + }); + } + + reload() { + this.load(this.filter); + } + + /** + * Render the process list + * + * @param processInstances + */ + private renderProcessInstances(processInstances: any[]) { + processInstances = this.optimizeProcessNames(processInstances); + this.data = new ObjectDataTableAdapter( + processInstances, + this.schemaColumn + ); + } + + /** + * Check if the list is empty + * @returns {ObjectDataTableAdapter|boolean} + */ + isListEmpty(): boolean { + return this.data === undefined || + (this.data && this.data.getRows() && this.data.getRows().length === 0); + } + + /** + * Emit the event rowClick passing the current task id when the row is clicked + * @param event + */ + onRowClick(event: DataRowEvent) { + let item = event; + this.currentProcessInstanceId = item.value.getValue('id'); + this.rowClick.emit(this.currentProcessInstanceId); + } + + /** + * Optimize process name field + * @param tasks + * @returns {any[]} + */ + private optimizeProcessNames(tasks: any[]) { + tasks = tasks.map(t => { + t.name = t.name || 'No name'; + if (t.name.length > 50) { + t.name = t.name.substring(0, 50) + '...'; + } + return t; + }); + return tasks; + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.css b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.css new file mode 100644 index 0000000000..7e9e64291f --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.css @@ -0,0 +1,11 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} + +.material-icons:hover { + color: rgb(255, 152, 0); +} diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.html new file mode 100644 index 0000000000..e5ca33e2be --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.html @@ -0,0 +1,23 @@ + + + +

    {{'START_PROCESS.DIALOG.TITLE'|translate}}

    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.ts new file mode 100644 index 0000000000..d51de06c9e --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.ts @@ -0,0 +1,101 @@ +/*! + * @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, Input, OnInit, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiProcessService } from './../services/activiti-process.service'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-start-process', + moduleId: __moduleName, + templateUrl: './activiti-start-process.component.html', + styleUrls: ['./activiti-start-process.component.css'], + providers: [ActivitiProcessService], + pipes: [ AlfrescoPipeTranslate ] + +}) +export class ActivitiStartProcessButton implements OnInit { + + @Input() + appId: string; + + @ViewChild('dialog') + dialog: any; + + processDefinitions: any[] = []; + + name: string; + processDefinition: string; + + /** + * Constructor + * @param auth + * @param translate + * @param activitiProcess + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiProcess: ActivitiProcessService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-processlist/src'); + } + } + + ngOnInit() { + this.load(this.appId); + } + + public load(appId: string) { + this.activitiProcess.getProcessDefinitions(this.appId).subscribe( + (res: any[]) => { + this.processDefinitions = res; + }, + (err) => { + console.log(err); + } + ); + } + + public showDialog() { + if (this.dialog) { + this.dialog.nativeElement.showModal(); + } + } + + public startProcess() { + if (this.processDefinition && this.name) { + this.activitiProcess.startProcess(this.processDefinition, this.name).subscribe( + (res: any) => { + this.cancel(); + }, + (err) => { + console.log(err); + } + ); + } + } + + public cancel() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/i18n/en.json b/ng2-components/ng2-activiti-processlist/src/i18n/en.json new file mode 100644 index 0000000000..5c45ffab35 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/i18n/en.json @@ -0,0 +1,54 @@ +{ + "PROCESSLIST": { + "NONE": "No process instances were found", + "SUMMARY": "Found {{total}} process instances", + "ERROR": "An error occurred while loading the processes instances: {{errorMessage}}", + "COLUMN": { + "NAME": "Name" + } + }, + "FILTERS": { + "MESSAGES": { + "NONE": "No process instance filter selected." + } + }, + "DETAILS": { + "LABELS": { + "STARTED_BY": "Started by", + "STARTED": "Started", + "COMMENTS": "Comments", + "TASKS_ACTIVE": "Active Tasks", + "TASKS_COMPLETED": "Completed Tasks", + "TASK_SUBTITLE": "Assigned to {{user}}, created {{created}}" + }, + "BUTTON": { + "CANCEL": "Cancel Process" + }, + "MESSAGES": { + "NONE": "No process details found." + }, + "TASKS": { + "NO_ACTIVE": "No tasks are currently active", + "NO_COMPLETED": "No tasks have been completed yet", + "TASK_DETAILS": "Task details", + "TASK_CLOSE": "Close" + }, + "COMMENTS": { + "NONE": "No comments." + } + }, + "START_PROCESS": { + "BUTTON": "Start Process", + "DIALOG": { + "TITLE": "Start Process", + "LABEL": { + "TYPE": "Type", + "NAME": "Name" + }, + "ACTION": { + "START": "Start", + "CANCEL": "Cancel" + } + } + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/models/comment.model.ts b/ng2-components/ng2-activiti-processlist/src/models/comment.model.ts new file mode 100644 index 0000000000..d275e7ca1e --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/models/comment.model.ts @@ -0,0 +1,38 @@ +/*! + * @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. + */ + +/** + * + * Comment submitted against a process + * + * @returns {Comment} . + */ +import { User } from './user.model'; + +export class Comment { + id: number; + message: string; + created: string; + createdBy: User; + + constructor(id: number, message: string, created: string, createdBy: User) { + this.id = id; + this.message = message; + this.created = created; + this.createdBy = createdBy; + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/models/filter.model.ts b/ng2-components/ng2-activiti-processlist/src/models/filter.model.ts new file mode 100644 index 0000000000..2cd0c0eb42 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/models/filter.model.ts @@ -0,0 +1,60 @@ +/*! + * @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. + */ + +/** + * + * This object represent the filter. + * + * + * @returns {FilterModel} . + */ +export class FilterModel { + id: number; + name: string; + recent: boolean = false; + icon: string; + filter: FilterParamsModel; + appId: number; + + constructor(name: string, recent: boolean, icon: string, query: string, state: string, assignment: string, appDefinitionId?: string) { + this.name = name; + this.recent = recent; + this.icon = icon; + this.filter = new FilterParamsModel(query, state, assignment, appDefinitionId); + } +} + +/** + * + * This object represent the parameters of a filter. + * + * + * @returns {FilterModel} . + */ +export class FilterParamsModel { + name: string; + sort: string; + state: string; + appDefinitionId: string; + + constructor(query: string, sort: string, state: string, appDefinitionId?: string) { + this.name = query; + this.sort = sort; + this.state = state; + this.appDefinitionId = appDefinitionId; + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/models/process-instance.ts b/ng2-components/ng2-activiti-processlist/src/models/process-instance.ts new file mode 100644 index 0000000000..1511e557be --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/models/process-instance.ts @@ -0,0 +1,39 @@ +/*! + * @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 ProcessInstance { + + public businessKey: string; + public ended: any; + public graphicalNotationDefined: boolean; + public id: string; + public name: string; + public processDefinitionCategory: string; + public processDefinitionDeploymentId: string; + public processDefinitionDescription: string; + public processDefinitionId: string; + public processDefinitionKey: string; + public processDefinitionName: string; + public processDefinitionVersion: number; + public startFormDefined: boolean; + public started: string; + public startedBy: any; + public suspended: boolean; + public tenantId: string; + public variables: any; + +} diff --git a/ng2-components/ng2-activiti-processlist/src/models/task-details.model.ts b/ng2-components/ng2-activiti-processlist/src/models/task-details.model.ts new file mode 100644 index 0000000000..5ab0361aaa --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/models/task-details.model.ts @@ -0,0 +1,93 @@ +/*! + * @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. + */ + +/** + * + * This object represent the details of a task. + * + * + * @returns {TaskDetailsModel} . + */ +import { User } from './user.model'; + +export class TaskDetailsModel { + id: string; + name: string; + assignee: User; + priority: number; + adhocTaskCanBeReassigned: number; + category: string; + created: string; + description: string; + dueDate: string; + duration: string; + endDate: string; + executionId: string; + formKey: string; + initiatorCanCompleteTask: boolean = false; + managerOfCandidateGroup: boolean = false; + memberOfCandidateGroup: boolean = false; + memberOfCandidateUsers: boolean = false; + involvedPeople: User []; + parentTaskId: string; + parentTaskName: string; + processDefinitionCategory: string; + processDefinitionDeploymentId: string; + processDefinitionDescription: string; + processDefinitionId: string; + processDefinitionKey: string; + processDefinitionName: string; + processDefinitionVersion: number = 0; + processInstanceId: string; + processInstanceName: string; + processInstanceStartUserId: string; + taskDefinitionKey: string; + + + constructor(obj: any) { + this.id = obj.id; + this.name = obj.name; + this.priority = obj.priority; + this.assignee = new User(obj.assignee.id, obj.assignee.email, obj.assignee.firstName, obj.assignee.lastName); + this.adhocTaskCanBeReassigned = obj.adhocTaskCanBeReassigned; + this.created = obj.created; + this.description = obj.description; + this.dueDate = obj.dueDate; + this.duration = obj.duration; + this.endDate = obj.endDate; + this.executionId = obj.executionId; + this.formKey = obj.formKey; + this.initiatorCanCompleteTask = obj.initiatorCanCompleteTask; + this.managerOfCandidateGroup = obj.managerOfCandidateGroup; + this.memberOfCandidateGroup = obj.memberOfCandidateGroup; + this.memberOfCandidateUsers = obj.memberOfCandidateUsers; + this.involvedPeople = obj.involvedPeople; + this.parentTaskId = obj.parentTaskId; + this.parentTaskName = obj.parentTaskName; + this.processDefinitionCategory = obj.processDefinitionCategory; + this.processDefinitionDeploymentId = obj.processDefinitionDeploymentId; + this.processDefinitionDescription = obj.processDefinitionDescription; + this.processDefinitionId = obj.processDefinitionId; + this.processDefinitionKey = obj.processDefinitionKey; + this.processDefinitionName = obj.processDefinitionName; + this.processDefinitionVersion = obj.processDefinitionVersion; + this.processInstanceId = obj.processInstanceId; + this.processInstanceName = obj.processInstanceName; + this.processInstanceStartUserId = obj.processInstanceStartUserId; + this.taskDefinitionKey = obj.taskDefinitionKey; + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/models/user.model.ts b/ng2-components/ng2-activiti-processlist/src/models/user.model.ts new file mode 100644 index 0000000000..6d21ad4e0b --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/models/user.model.ts @@ -0,0 +1,38 @@ +/*! + * @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. + */ + +/** + * + * This object represent the user. + * + * + * @returns {User} . + */ + +export class User { + id: number; + email: string; + firstName: string; + lastName: string; + + constructor(id: number, email: string, firstName: string, lastName: string) { + this.id = id; + this.email = email; + this.firstName = firstName; + this.lastName = lastName; + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts new file mode 100644 index 0000000000..ddfe2d0ab9 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts @@ -0,0 +1,51 @@ +/*! + * @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 { it, describe, expect, beforeEachProviders, beforeEach, inject } from '@angular/core/testing'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { ActivitiProcessService } from './activiti-process.service'; +// import { ProcessInstance } from '../models/process-instance'; + +describe('ActivitiProcessService', () => { + + let processService; + + beforeEachProviders(() => { + return [ + ActivitiProcessService, + AlfrescoSettingsService, + AlfrescoAuthenticationService + ]; + }); + + beforeEach(inject([ActivitiProcessService], (service: ActivitiProcessService) => { + processService = service; + })); + + it('should get process instances', (done) => { + + expect(true).toBe(true); + done(); + + // processService.getProcesses().subscribe((instances: ProcessInstance[]) => { + // expect(instances.length).toBe(1); + // expect(instances[0].id).toBe('myprocess:1'); + // expect(instances[0].name).toBe('my process'); + // done(); + // }); + }); +}); diff --git a/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts new file mode 100644 index 0000000000..9f5926172e --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts @@ -0,0 +1,163 @@ +/*! + * @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 {AlfrescoAuthenticationService} from 'ng2-alfresco-core'; +import {ProcessInstance} from '../models/process-instance'; +import {FilterModel} from '../models/filter.model'; +import {User} from '../models/user.model'; +import {Comment} from '../models/comment.model'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; + +@Injectable() +export class ActivitiProcessService { + + constructor(public authService: AlfrescoAuthenticationService) { + } + + /** + * Retrive all the Deployed app + * @returns {Observable} + */ + getDeployedApplications(name: string): Observable { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.appsApi.getAppDefinitions()) + .map((response: any) => response.data.find(p => p.name === name)) + .do(data => console.log('Application: ' + JSON.stringify(data))); + } + + getProcesses(): Observable { + let request = {'page': 0, 'sort': 'created-desc', 'state': 'all'}; + + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstances(request)) + .map(this.extractData) + .catch(this.handleError); + } + + getProcessInstances(filter: FilterModel): Observable { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstances(filter)) + .map(this.extractData) + .catch(this.handleError); + } + + getProcessFilters(appId: string): Observable { + let filterOpts = appId ? { + appId: appId + } : {}; + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.userFiltersApi.getUserProcessInstanceFilters(filterOpts)) + .map(this.extractData) + .catch(this.handleError); + } + + getProcess(id: string): Observable { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstance(id)) + .catch(this.handleError); + } + + getProcessTasks(id: string, state: string): Observable { + let taskOpts = state ? { + processInstanceId: id, + state: state + } : { + processInstanceId: id + }; + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.listTasks(taskOpts)) + .map(this.extractData) + .map(tasks => tasks.map((task: any) => { + task.created = new Date(task.created); + return task; + })) + .catch(this.handleError); + } + + /** + * Retrive all the process instance's comments + * @param id - process instance ID + * @returns {} + */ + getProcessInstanceComments(id: string): Observable { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.commentsApi.getProcessInstanceComments(id)) + .map(res => res) + .map((response: any) => { + let comments: Comment[] = []; + response.data.forEach((comment) => { + let user = new User( + comment.createdBy.id, comment.createdBy.email, comment.createdBy.firstName, comment.createdBy.lastName); + comments.push(new Comment(comment.id, comment.message, comment.created, user)); + }); + return comments; + }).catch(this.handleError); + } + + /** + * Add a comment to a process instance + * @param id - process instance Id + * @param message - content of the comment + * @returns {Comment} + */ + addProcessInstanceComment(id: string, message: string): Observable { + return Observable.fromPromise( + this.authService.getAlfrescoApi().activiti.commentsApi.addProcessInstanceComment({message: message}, id) + ) + .map(res => res) + .map((response: Comment) => { + return new Comment(response.id, response.message, response.created, response.createdBy); + }).catch(this.handleError); + + } + + getProcessDefinitions(appId: string) { + let opts = appId ? { + latest: true, + appId: appId + } : { + latest: true + }; + return Observable.fromPromise( + this.authService.getAlfrescoApi().activiti.processApi.getProcessDefinitions(opts) + ) + .map(this.extractData) + .catch(this.handleError); + } + + startProcess(processDefinitionId: string, name: string) { + let startRequest: any = {}; + startRequest.name = name; + startRequest.processDefinitionId = processDefinitionId; + return Observable.fromPromise( + this.authService.getAlfrescoApi().activiti.processApi.startNewProcessInstance(startRequest) + ) + .catch(this.handleError); + } + + cancelProcess(processInstanceId: string) { + return Observable.fromPromise( + this.authService.getAlfrescoApi().activiti.processApi.deleteProcessInstance(processInstanceId) + ) + .catch(this.handleError); + } + + private extractData(res: any) { + return res.data || {}; + } + + private handleError(error: any) { + console.error(error); + return Observable.throw(error || 'Server error'); + } +} diff --git a/ng2-components/ng2-activiti-processlist/tsconfig.json b/ng2-components/ng2-activiti-processlist/tsconfig.json new file mode 100644 index 0000000000..e4d2ae201a --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "removeComments": true, + "declaration": true, + "noLib": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": false, + "noImplicitReturns": false, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "outDir": "dist" + }, + "exclude": [ + "demo", + "node_modules", + "typings/main", + "typings/main.d.ts", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-processlist/tslint.json b/ng2-components/ng2-activiti-processlist/tslint.json new file mode 100644 index 0000000000..ba706079f4 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/tslint.json @@ -0,0 +1,121 @@ +{ + "rules": { + "align": [ + true, + "parameters", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "interface-name": false, + "jsdoc-format": true, + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 180 + ], + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-any": false, + "no-arg": true, + "no-bitwise": false, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-constructor-vars": false, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unreachable": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "radix": true, + "semicolon": true, + "switch-default": true, + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef": false, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "use-strict": false, + "variable-name": [ + true, + "check-format", + "allow-leading-underscore", + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-operator", + "check-separator", + "check-type", + "check-module", + "check-decl" + ] + } +} diff --git a/ng2-components/ng2-activiti-processlist/typings.json b/ng2-components/ng2-activiti-processlist/typings.json new file mode 100644 index 0000000000..7e0e18568d --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/typings.json @@ -0,0 +1,7 @@ +{ + "globalDependencies": { + "core-js": "registry:dt/core-js#0.0.0+20160317120654", + "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", + "node": "registry:dt/node#4.0.0+20160509154515" + } +} diff --git a/ng2-components/ng2-activiti-tasklist/.editorconfig b/ng2-components/ng2-activiti-tasklist/.editorconfig new file mode 100644 index 0000000000..75a2477db7 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/.editorconfig @@ -0,0 +1,23 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[package.json] +indent_style = space +indent_size = 2 + +[karma.conf.js] +indent_style = space +indent_size = 2 + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/ng2-components/ng2-activiti-tasklist/.gitignore b/ng2-components/ng2-activiti-tasklist/.gitignore new file mode 100644 index 0000000000..13324e66b9 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/.gitignore @@ -0,0 +1,15 @@ +npm-debug.log +node_modules +.idea +typings +coverage +dist +src/**/*.js +src/**/*.js.map +src/**/*.d.ts +demo/**/*.js +demo/**/*.js.map +demo/**/*.d.ts +index.js +index.js.map +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-tasklist/.npmignore b/ng2-components/ng2-activiti-tasklist/.npmignore new file mode 100644 index 0000000000..16ba3e61e5 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/.npmignore @@ -0,0 +1,16 @@ +npm-debug.log +.idea + +assets/ +coverage/ +node_modules +typings/ +fonts/ + +/.editorconfig +/.travis.yml +/*.js +/*.json +/*.ts +/*.js.map +/.npmignore diff --git a/ng2-components/ng2-activiti-tasklist/LICENSE b/ng2-components/ng2-activiti-tasklist/LICENSE new file mode 100644 index 0000000000..430d42bbea --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/ng2-components/ng2-activiti-tasklist/README.md b/ng2-components/ng2-activiti-tasklist/README.md new file mode 100644 index 0000000000..e6a5be262c --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/README.md @@ -0,0 +1,160 @@ +# Activiti Task List Component for Angular 2 + +## Prerequisites + +Before you start using this development framework, make sure you have installed all required software and done all the +necessary configuration, see this [page](https://github.com/Alfresco/alfresco-ng2-components/blob/master/PREREQUISITES.md). + +## Install + +```sh +npm install --save ng2-activiti-tasklist +``` + +### Dependencies + +Add the following dependency to your index.html: + +```html + +``` + +You must separately install the following libraries for your application: + +- [ng2-translate](https://github.com/ocombe/ng2-translate) +- [ng2-alfresco-core](https://www.npmjs.com/package/ng2-alfresco-core) +- [ng2-alfresco-datatable](https://www.npmjs.com/package/ng2-alfresco-datatable) + + +```sh +npm install --save ng2-translate ng2-alfresco-core ng2-alfresco-datatable +``` + +#### Material Design Lite + +The style of this component is based on [material design](https://getmdl.io/), so if you want to visualize it correctly you have to add the material +design dependency to your project: + +```sh +npm install --save material-design-icons material-design-lite +``` + +Also make sure you include these dependencies in your `index.html` file: + +```html + + + + +``` + +## Basic usage example Activiti Task List +The component shows the list of all the tasks filter by the +FilterModel passed in input. +```html + +``` + +#### Events +**onSuccess**: The event is emitted when the task list is loaded +**rowClick**: The event is emitted when the task in the list is +clicked
    + +#### Options + +**taskFilter**: { FilterModel } required) FilterModel object that +is passed to the task list API to filter the task list. +Example: +```json +{ + "id": 4, + "name": "Involved Tasks", + "recent": false, + "icon": "glyphicon-align-left", + "filter": { + "appDefinitionId": "1", + "processDefinitionId": "1533", + "sort": "created-desc", + "name": "", + "state": "open", + "assignment": "involved", + "page": "0", + "size": "25" + } +} +``` +**schemaColumn**: { any[] } optional) JSON object that represent +the number and the type of the columns that you want show +Example: +```json +[ + {type: 'text', key: 'id', title: 'Id'}, + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'formKey', title: 'Form Key', sortable: true}, + {type: 'text', key: 'created', title: 'Created', sortable: true} +] +``` + +## Basic usage example Activiti Task Details +The component shows the details of the task id passed in input +```html + +``` + +#### Events +No events + +#### Options + +**taskId**: { string } required) The id of the task details that we +are asking for. + +## Basic usage example Activiti Filter +The component shows all the available filters. + +```html + +``` + +#### Events +**filterClick**: The event is emitted when the filter in the list is + clicked + +#### Options +No options + +## Build from sources + +Alternatively you can build component from sources with the following commands: + +```sh +npm install +npm run build +``` + +### Build the files and keep watching for changes + +```sh +$ npm run build:w +``` + +### Running unit tests + +```sh +npm test +``` + +### Running unit tests in browser + +```sh +npm test-browser +``` + +This task rebuilds all the code, runs tslint, license checks and other quality check tools +before performing unit testing. + +### Code coverage + +```sh +npm run coverage +``` diff --git a/ng2-components/ng2-activiti-tasklist/assets/license_header.txt b/ng2-components/ng2-activiti-tasklist/assets/license_header.txt new file mode 100644 index 0000000000..83fd1531a3 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/assets/license_header.txt @@ -0,0 +1,16 @@ +/*! + * @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. + */ \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/demo/.gitignore b/ng2-components/ng2-activiti-tasklist/demo/.gitignore new file mode 100644 index 0000000000..25beca4c27 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/.gitignore @@ -0,0 +1,6 @@ +node_modules +.idea +coverage +dist +typings +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-tasklist/demo/.npmignore b/ng2-components/ng2-activiti-tasklist/demo/.npmignore new file mode 100644 index 0000000000..c51c008259 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/.npmignore @@ -0,0 +1,3 @@ +node_modules +dist +typings \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/demo/README.md b/ng2-components/ng2-activiti-tasklist/demo/README.md new file mode 100644 index 0000000000..28b3ffd976 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/README.md @@ -0,0 +1,13 @@ +# DataTable demo + +Install: + +``` +npm install +``` + +Run the project: + +``` +npm start +``` \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/demo/index.html b/ng2-components/ng2-activiti-tasklist/demo/index.html new file mode 100644 index 0000000000..5b926def7a --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/index.html @@ -0,0 +1,34 @@ + + + + + Angular 2 TaskList - Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ng2-components/ng2-activiti-tasklist/demo/package.json b/ng2-components/ng2-activiti-tasklist/demo/package.json new file mode 100644 index 0000000000..e80059f6b5 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/package.json @@ -0,0 +1,81 @@ +{ + "name": "ng2-activiti-tasklist-demo", + "description": "Alfresco Angular2 DataTable Component - Demo", + "version": "0.1.0", + "author": "Alfresco Software, Ltd.", + "main": "index.js", + "scripts": { + "clean": "rimraf dist node_modules typings", + "postinstall": "npm run typings && npm run build", + "typings": "typings install", + "start": "npm run build && concurrently \"npm run tsc:w\" \"npm run server\" ", + "server": "wsrv -o -s -l", + "build": "npm run tslint && rimraf dist && npm run tsc", + "tsc": "tsc", + "tsc:w": "tsc -w", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts" + }, + "license": "Apache-2.0", + "dependencies": { + "@angular/common": "2.0.0-rc.3", + "@angular/compiler": "2.0.0-rc.3", + "@angular/core": "2.0.0-rc.3", + "@angular/forms": "0.1.1", + "@angular/http": "2.0.0-rc.3", + "@angular/platform-browser": "2.0.0-rc.3", + "@angular/platform-browser-dynamic": "2.0.0-rc.3", + "@angular/router": "3.0.0-alpha.7", + "@angular/router-deprecated": "2.0.0-rc.2", + "@angular/upgrade": "2.0.0-rc.3", + "systemjs": "0.19.27", + "core-js": "2.4.0", + "reflect-metadata": "0.1.3", + "rxjs": "5.0.0-beta.6", + "zone.js": "0.6.12", + "license-check": "1.1.5", + "material-design-icons": "2.2.3", + "material-design-lite": "1.1.3", + "ng2-translate": "2.2.2", + "alfresco-js-api": "^0.3.0", + "ng2-alfresco-datatable": "^0.1.12", + "ng2-alfresco-core": "^0.1.36" + }, + "devDependencies": { + "browser-sync": "2.10.0", + "concurrently": "2.0.0", + "rimraf": "2.5.2", + "tslint": "3.8.1", + "typescript": "1.8.10", + "typings": "1.0.4", + "wsrv": "0.1.3" + }, + "keywords": [ + "angular2", + "typescript" + ], + "license-check-config": { + "src": [ + "**/*.js", + "**/*.ts", + "!/**/coverage/**/*", + "!/**/demo/**/*", + "!/**/node_modules/**/*", + "!/**/typings/**/*", + "!*.js" + ], + "contributors": [ + { + "name": "Maurizio Vitale", + "email": "maurizio.vitale84@gmail.com" + }, + { + "name": "Eugenio Romano", + "email": "eugenio.romano@alfresco.com" + } + ], + "path": "assets/license_header.txt", + "blocking": true, + "logInfo": false, + "logError": true + } +} diff --git a/ng2-components/ng2-activiti-tasklist/demo/src/main.ts b/ng2-components/ng2-activiti-tasklist/demo/src/main.ts new file mode 100644 index 0000000000..28fa067a7d --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/src/main.ts @@ -0,0 +1,100 @@ +/*! + * @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, OnInit} from '@angular/core'; +import {ALFRESCO_CORE_PROVIDERS, AlfrescoAuthenticationService, AlfrescoSettingsService} from 'ng2-alfresco-core'; +import {bootstrap} from '@angular/platform-browser-dynamic'; +import {ActivitiTaskList} from 'ng2-activiti-tasklist'; +import {ObjectDataTableAdapter, ObjectDataColumn} from 'ng2-alfresco-datatable'; +import {HTTP_PROVIDERS} from '@angular/http'; + +declare let AlfrescoApi: any; + +@Component({ + selector: 'activiti-tasklist-demo', + template: `label for="token">Insert a valid access token / ticket:
    +
    +
    +

    +
    + Authentication failed to ip {{ bpmHost }} with user: admin, admin, you can still try to add a valid token to perform + operations. +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    `, + styles: [ + ':host > .container {padding: 10px}', + '.p-10 { padding: 10px; }' + ], + directives: [ActivitiTaskList] +}) +class ActivitiTaskListDemo implements OnInit { + + bpmHost: string = 'http://127.0.0.1:9999'; + + token: string; + + data: ObjectDataTableAdapter; + + authenticated: boolean; + + constructor(private authService: AlfrescoAuthenticationService, + private settingsService: AlfrescoSettingsService) { + this.settingsService.setProviders('BPM'); + this.data = new ObjectDataTableAdapter([], []); + } + + ngOnInit() { + this.login(); + + let schema = [ + {type: 'text', key: 'id', title: 'Id'}, + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'formKey', title: 'Form Key', sortable: true}, + {type: 'text', key: 'created', title: 'Created', sortable: true} + ]; + + let columns = schema.map(col => new ObjectDataColumn(col)); + this.data.setColumns(columns); + } + + login() { + this.authService.login('admin', 'admin').subscribe( + token => { + console.log(token); + this.token = token; + this.authenticated = true; + }, + error => { + console.log(error); + this.authenticated = false; + }); + } +} + +bootstrap(ActivitiTaskListDemo, [ + HTTP_PROVIDERS, + ALFRESCO_CORE_PROVIDERS] +); diff --git a/ng2-components/ng2-activiti-tasklist/demo/systemjs.config.js b/ng2-components/ng2-activiti-tasklist/demo/systemjs.config.js new file mode 100644 index 0000000000..4a716d3e2a --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/systemjs.config.js @@ -0,0 +1,57 @@ +/** + * System configuration for Angular 2 samples + * Adjust as necessary for your application needs. + */ +(function(global) { + // map tells the System loader where to look for things + var map = { + 'app': 'dist', // 'dist', + '@angular': 'node_modules/@angular', + 'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api', + 'rxjs': 'node_modules/rxjs', + + 'ng2-translate': 'node_modules/ng2-translate', + 'ng2-alfresco-core': 'node_modules/ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'node_modules/ng2-alfresco-datatable/dist', + 'ng2-activiti-tasklist': 'node_modules/ng2-activiti-tasklist/dist' + }; + // packages tells the System loader how to load when no filename and/or no extension + var packages = { + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' }, + + 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-activiti-tasklist': { main: 'index.js', defaultExtension: 'js' } + }; + var ngPackageNames = [ + 'common', + 'compiler', + 'core', + 'http', + 'platform-browser', + 'platform-browser-dynamic', + 'router', + 'router-deprecated', + 'upgrade' + ]; + // Individual files (~300 requests): + function packIndex(pkgName) { + packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' }; + } + // Bundled (~40 requests): + function packUmd(pkgName) { + packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' }; + } + // Most environments should use UMD; some (Karma) need the individual index files + var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; + // Add package entries for angular packages + ngPackageNames.forEach(setPackageConfig); + var config = { + map: map, + packages: packages + }; + System.config(config); +})(this); diff --git a/ng2-components/ng2-activiti-tasklist/demo/tsconfig.json b/ng2-components/ng2-activiti-tasklist/demo/tsconfig.json new file mode 100644 index 0000000000..f6761b5218 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "removeComments": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "typings/main", + "typings/main.d.ts" + ] +} diff --git a/ng2-components/ng2-activiti-tasklist/demo/tslint.json b/ng2-components/ng2-activiti-tasklist/demo/tslint.json new file mode 100644 index 0000000000..8c48e76469 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/tslint.json @@ -0,0 +1,124 @@ +{ + "rules": { + "align": [ + true, + "parameters", + "arguments", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space", + "check-lowercase" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "interface-name": false, + "jsdoc-format": true, + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 140 + ], + "member-ordering": [ + true, + "public-before-private", + "static-before-instance", + "variables-before-functions" + ], + "no-any": false, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-constructor-vars": false, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unreachable": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "radix": true, + "semicolon": true, + "switch-default": true, + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef": false, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "use-strict": false, + "variable-name": [ + true, + "check-format", + "allow-leading-underscore", + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-operator", + "check-separator", + "check-type", + "check-module", + "check-decl" + ] + } +} diff --git a/ng2-components/ng2-activiti-tasklist/demo/typings.json b/ng2-components/ng2-activiti-tasklist/demo/typings.json new file mode 100644 index 0000000000..d8954c2485 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/demo/typings.json @@ -0,0 +1,7 @@ +{ + "globalDependencies": { + "core-js": "registry:dt/core-js#0.0.0+20160317120654", + "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", + "node": "registry:dt/node#4.0.0+20160509154515" + } +} diff --git a/ng2-components/ng2-activiti-tasklist/index.ts b/ng2-components/ng2-activiti-tasklist/index.ts new file mode 100644 index 0000000000..dc72a4b407 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/index.ts @@ -0,0 +1,24 @@ +/*! + * @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 { ActivitiTaskList } from './src/components/activiti-tasklist.component'; +import { ActivitiTaskDetails } from './src/components/activiti-task-details.component'; +import { ActivitiFilters } from './src/components/activiti-filters.component'; + +export * from './src/components/activiti-tasklist.component'; + +export const ALFRESCO_TASKLIST_DIRECTIVES: [any] = [ActivitiFilters, ActivitiTaskList, ActivitiTaskDetails]; diff --git a/ng2-components/ng2-activiti-tasklist/karma-test-shim.js b/ng2-components/ng2-activiti-tasklist/karma-test-shim.js new file mode 100644 index 0000000000..bffeaa9753 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/karma-test-shim.js @@ -0,0 +1,109 @@ +// Tun on full stack traces in errors to help debugging +Error.stackTraceLimit = Infinity; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +__karma__.loaded = function() {}; + +var map = { + 'app': 'base/dist', + 'rxjs': 'base/node_modules/rxjs', + '@angular': 'base/node_modules/@angular', + 'ng2-translate' : '/base/node_modules/ng2-translate', + 'ng2-alfresco-core': '/base/node_modules/ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': '/base/node_modules/ng2-alfresco-datatable/dist' +}; + +var packages = { + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, + 'ng2-alfresco-datatable': { main: 'index.js', defaultExtension: 'js' } +}; + +var packageNames = [ + '@angular/common', + '@angular/compiler', + '@angular/core', + '@angular/http', + '@angular/platform-browser', + '@angular/platform-browser-dynamic', + '@angular/router', + '@angular/router-deprecated', + '@angular/testing', + '@angular/upgrade' +]; + +packageNames.forEach(function(pkgName) { + packages[pkgName] = { main: 'index.js', defaultExtension: 'js' }; +}); + +packages['base/dist'] = { + defaultExtension: 'js', + format: 'register', + map: Object.keys(window.__karma__.files).filter(onlyAppFiles).reduce(createPathRecords, {}) +}; + +var config = { + map: map, + packages: packages +}; + +System.config(config); + +System.import('@angular/platform-browser/src/browser/browser_adapter') + .then(function(browser_adapter) { browser_adapter.BrowserDomAdapter.makeCurrent(); }) + .then(function () { + return Promise.all([ + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') + ]) + }) + .then(function (providers) { + var testing = providers[0]; + var testingBrowser = providers[1]; + + testing.setBaseTestProviders( + testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, + testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS); + + }) + .then(function() { return Promise.all(resolveTestFiles()); }) + .then( + function() { + __karma__.start(); + }, + function(error) { + if(typeof __karma__.error == 'function') { + __karma__.error(error.stack || error); + }else{ + console.error(error); + } + } + ); +function createPathRecords(pathsMapping, appPath) { + var pathParts = appPath.split('/'); + var moduleName = './' + pathParts.slice(Math.max(pathParts.length - 2, 1)).join('/'); + moduleName = moduleName.replace(/\.js$/, ''); + pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath]; + return pathsMapping; +} + +function onlyAppFiles(filePath) { + return /\/base\/dist\/(?!.*\.spec\.js$).*\.js$/.test(filePath); +} + +function onlySpecFiles(path) { + return /\.spec\.js$/.test(path); +} + +function resolveTestFiles() { + return Object.keys(window.__karma__.files) // All files served by Karma. + .filter(onlySpecFiles) + .map(function(moduleName) { + // loads all spec files via their global module names (e.g. + // 'base/dist/vg-player/vg-player.spec') + return System.import(moduleName); + }); +} diff --git a/ng2-components/ng2-activiti-tasklist/karma.conf.js b/ng2-components/ng2-activiti-tasklist/karma.conf.js new file mode 100644 index 0000000000..07db0170aa --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/karma.conf.js @@ -0,0 +1,100 @@ +'use strict'; + +module.exports = function (config) { + var configuration = { + basePath: '.', + + frameworks: ['jasmine-ajax', 'jasmine'], + + files: [ + // paths loaded by Karma + {pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: true}, + {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: false}, + {pattern: 'node_modules/zone.js/dist/zone.js', included: true, watched: true}, + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.map', included: false, watched: false}, + {pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false}, + {pattern: 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', included: true, watched: false}, + + {pattern: 'karma-test-shim.js', included: true, watched: true}, + + // paths loaded via module imports + {pattern: 'dist/**/*.js', included: false, watched: true}, + {pattern: 'dist/**/*.html', included: true, served: true, watched: true}, + {pattern: 'dist/**/*.css', included: true, served: true, watched: true}, + + // paths to support debugging with source maps in dev tools + {pattern: 'src/**/*.ts', included: false, watched: false}, + {pattern: 'dist/**/*.js.map', included: false, watched: false} + ], + + exclude: [ + 'node_modules/**/*spec.js' + ], + + // proxied base paths + proxies: { + // required for component assets fetched by Angular's compiler + '/src/': '/base/src/' + }, + + port: 9876, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + colors: true, + + autoWatch: true, + + browsers: ['Chrome'], + + customLaunchers: { + Chrome_travis_ci: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + + // Karma plugins loaded + plugins: [ + 'karma-jasmine', + 'karma-coverage', + 'karma-jasmine-ajax', + 'karma-chrome-launcher', + 'karma-mocha-reporter', + 'karma-jasmine-html-reporter' + ], + + // Coverage reporter generates the coverage + reporters: ['mocha', 'coverage', 'coveralls', 'kjhtml'], + + // Source files that you wanna generate coverage for. + // Do not include tests or libraries (these files will be instrumented by Istanbul) + preprocessors: { + 'dist/**/!(*spec).js': ['coverage'] + }, + + coverageReporter: { + dir: 'coverage/', + subdir: 'report', + reporters: [ + {type: 'text'}, + {type: 'json', file: 'coverage-final.json'}, + {type: 'html'}, + {type: 'lcov'} + ] + } + }; + + if (process.env.TRAVIS) { + configuration.browsers = ['Chrome_travis_ci']; + } + + config.set(configuration) +}; diff --git a/ng2-components/ng2-activiti-tasklist/package.json b/ng2-components/ng2-activiti-tasklist/package.json new file mode 100644 index 0000000000..93d860c6ab --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/package.json @@ -0,0 +1,110 @@ +{ + "name": "ng2-activiti-tasklist", + "description": "Activiti Angular2 Task List Component", + "version": "0.3.0", + "author": "Alfresco Software, Ltd.", + "scripts": { + "postinstall": "typings install", + "clean": "rimraf dist node_modules typings", + "typings": "typings install", + "server": "wsrv -o -p 9875", + "build": "npm run tslint && rimraf dist && tsc && npm run copy-dist && license-check", + "build:w": "npm run tslint && rimraf dist && npm run watch-task", + "watch-task": "concurrently \"npm run tsc:w\" \"npm run copy-dist:w\" \"license-check\"", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'src/{,**/}**.ts'", + "copy-dist": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src", + "copy-dist:w": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src -w", + "tsc": "tsc", + "tsc:w": "tsc -w", + "pretest": "npm run build", + "test": "karma start karma.conf.js --reporters mocha,coverage --single-run", + "test-browser": "concurrently \"karma start karma.conf.js --reporters kjhtml\" \"npm run watch-task\"", + "posttest": "node_modules/.bin/remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html && node_modules/.bin/remap-istanbul -i coverage/report/coverage-final.json -o coverage/report/coverage-final.json", + "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report", + "prepublish": "npm run build", + "travis": "npm link ng2-alfresco-core ng2-alfresco-datatable" + }, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/Alfresco/alfresco-ng2-components.git" + }, + "bugs": { + "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Maurizio Vitale", + "email": "maurizio.vitale84@gmail.com" + }, + { + "name": "Eugenio Romano", + "email": "eugenio.romano@alfresco.com" + } + ], + "keywords": [ + "ng2", + "angular", + "angular2", + "alfresco" + ], + "dependencies": { + "@angular/common": "2.0.0-rc.3", + "@angular/compiler": "2.0.0-rc.3", + "@angular/core": "2.0.0-rc.3", + "@angular/forms": "0.1.1", + "@angular/http": "2.0.0-rc.3", + "@angular/platform-browser": "2.0.0-rc.3", + "@angular/platform-browser-dynamic": "2.0.0-rc.3", + "@angular/router": "3.0.0-alpha.7", + "@angular/router-deprecated": "2.0.0-rc.2", + "@angular/upgrade": "2.0.0-rc.3", + "systemjs": "0.19.27", + "core-js": "2.4.0", + "reflect-metadata": "0.1.3", + "rxjs": "5.0.0-beta.6", + "zone.js": "0.6.12", + "ng2-translate": "2.2.2", + "ng2-alfresco-core": "0.3.0", + "ng2-alfresco-datatable": "0.3.0", + "ng2-activiti-form": "0.3.0", + "alfresco-js-api": "^0.3.0" + }, + "peerDependencies": { + "material-design-icons": "^2.2.3", + "material-design-lite": "^1.1.3" + }, + "devDependencies": { + "concurrently": "2.1.0", + "coveralls": "2.11.9", + "cpx": "1.3.1", + "jasmine-core": "2.4.1", + "karma": "0.13.22", + "karma-chrome-launcher": "1.0.1", + "karma-coverage": "1.0.0", + "karma-coveralls": "1.1.2", + "karma-jasmine": "1.0.2", + "karma-jasmine-ajax": "0.1.13", + "karma-mocha-reporter": "2.0.3", + "karma-jasmine-html-reporter": "0.2.0", + "license-check": "1.1.5", + "rimraf": "2.5.2", + "remap-istanbul": "0.6.3", + "traceur": "0.0.91", + "tslint": "3.8.1", + "typescript": "1.8.10", + "typings": "1.0.4", + "wsrv": "0.1.3" + }, + "license-check-config": { + "src": [ + "./dist/**/*.js" + ], + "path": "assets/license_header.txt", + "blocking": true, + "logInfo": false, + "logError": true + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/assets/translation.service.mock.ts b/ng2-components/ng2-activiti-tasklist/src/assets/translation.service.mock.ts new file mode 100644 index 0000000000..3ddf9a9050 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/assets/translation.service.mock.ts @@ -0,0 +1,37 @@ +/*! + * @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 { Observable } from 'rxjs/Rx'; +import { EventEmitter } from '@angular/core'; + +export interface LangChangeEvent { + lang: string; + translations: any; +} + +export class TranslationMock { + + public onLangChange: EventEmitter = new EventEmitter(); + + addTranslationFolder() { + + } + + public get(key: string|Array, interpolateParams?: Object): Observable { + return Observable.of(key); + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.css new file mode 100644 index 0000000000..7e9e64291f --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.css @@ -0,0 +1,11 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} + +.material-icons:hover { + color: rgb(255, 152, 0); +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html new file mode 100644 index 0000000000..9bfdf6e968 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html @@ -0,0 +1,33 @@ +{{ 'TASK_DETAILS.LABELS.CHECKLIST' | translate }} +
    add
    +
    + Add a checklist +
    + +
    + {{ 'TASK_DETAILS.CHECKLIST.NONE' | translate }} +
    + + +

    New Task

    +
    +
    + + +
    +
    +
    + + +
    +
    \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts new file mode 100644 index 0000000000..e2552ecaed --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts @@ -0,0 +1,120 @@ +/*! + * @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, Input, OnInit, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { TaskDetailsModel } from '../models/task-details.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-checklist', + moduleId: __moduleName, + templateUrl: './activiti-checklist.component.html', + styleUrls: ['./activiti-checklist.component.css'], + providers: [ActivitiTaskListService], + pipes: [AlfrescoPipeTranslate] + +}) +export class ActivitiChecklist implements OnInit { + + @Input() + taskId: string; + + @ViewChild('dialog') + dialog: any; + + taskName: string; + + checklist: TaskDetailsModel [] = []; + + private taskObserver: Observer; + task$: Observable; + + /** + * Constructor + * @param auth + * @param translate + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiTaskList: ActivitiTaskListService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + this.task$ = new Observable(observer => this.taskObserver = observer).share(); + } + + ngOnInit() { + this.task$.subscribe((task: TaskDetailsModel) => { + this.checklist.push(task); + }); + + if (this.taskId) { + this.load(this.taskId); + } + } + + public load(taskId: string) { + this.checklist = []; + if (this.taskId) { + this.activitiTaskList.getTaskChecklist(this.taskId).subscribe( + (res: TaskDetailsModel[]) => { + res.forEach((task) => { + this.taskObserver.next(task); + }); + }, + (err) => { + console.log(err); + } + ); + } else { + this.checklist = []; + } + } + + public showDialog() { + if (this.dialog) { + this.dialog.nativeElement.showModal(); + } + } + + public add() { + let newTask = new TaskDetailsModel({name: this.taskName, parentTaskId: this.taskId, assignee: {id: '1'}}); + this.activitiTaskList.addTask(newTask).subscribe( + (res: TaskDetailsModel) => { + this.checklist.push(res); + }, + (err) => { + console.log(err); + } + ); + + this.cancel(); + } + + public cancel() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.css new file mode 100644 index 0000000000..7e9e64291f --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.css @@ -0,0 +1,11 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} + +.material-icons:hover { + color: rgb(255, 152, 0); +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html new file mode 100644 index 0000000000..50fb144e73 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html @@ -0,0 +1,35 @@ +{{ 'TASK_DETAILS.LABELS.COMMENTS' |translate }} +
    add
    +
    + Add a comment +
    + + +
    + {{ 'TASK_DETAILS.COMMENTS.NONE' | translate }} +
    + + + +

    New comment

    +
    +
    + + +
    +
    +
    + + +
    +
    \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts new file mode 100644 index 0000000000..92bb57d444 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts @@ -0,0 +1,121 @@ +/*! + * @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, Input, OnInit, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { Comment } from '../models/comment.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-comments', + moduleId: __moduleName, + templateUrl: './activiti-comments.component.html', + styleUrls: ['./activiti-comments.component.css'], + providers: [ActivitiTaskListService], + pipes: [ AlfrescoPipeTranslate ] + +}) +export class ActivitiComments implements OnInit { + + @Input() + taskId: string; + + @ViewChild('dialog') + dialog: any; + + comments: Comment [] = []; + + private commentObserver: Observer; + comment$: Observable; + + message: string; + + /** + * Constructor + * @param auth + * @param translate + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiTaskList: ActivitiTaskListService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + + this.comment$ = new Observable(observer => this.commentObserver = observer).share(); + + } + + ngOnInit() { + this.comment$.subscribe((comment: Comment) => { + this.comments.push(comment); + }); + + if (this.taskId) { + this.load(this.taskId); + } + } + + public load(taskId: string) { + this.comments = []; + if (this.taskId) { + this.activitiTaskList.getTaskComments(this.taskId).subscribe( + (res: Comment[]) => { + res.forEach((comment) => { + this.commentObserver.next(comment); + }); + }, + (err) => { + console.log(err); + } + ); + } else { + this.comments = []; + } + } + + public showDialog() { + if (this.dialog) { + this.dialog.nativeElement.showModal(); + } + } + + public add() { + this.activitiTaskList.addTaskComment(this.taskId, this.message).subscribe( + (res: Comment) => { + this.comments.push(res); + this.message = ''; + }, + (err) => { + console.log(err); + } + ); + this.cancel(); + } + + public cancel() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.css new file mode 100644 index 0000000000..6b7e0a7a66 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.css @@ -0,0 +1,3 @@ +.mdl-list__item { + cursor: pointer; +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.html new file mode 100644 index 0000000000..580798c6a3 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.spec.ts new file mode 100644 index 0000000000..d1663d2596 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.spec.ts @@ -0,0 +1,115 @@ +/*! + * @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 { + it, + describe, + expect, + beforeEach +} from '@angular/core/testing'; + +import { ActivitiFilters } from './activiti-filters.component'; +import { ActivitiTaskListService } from '../services/activiti-tasklist.service'; +import { Observable } from 'rxjs/Rx'; +import { FilterModel } from '../models/filter.model'; + +describe('ActivitiFilters', () => { + + let filterList: ActivitiFilters; + + let fakeGlobalFilter = []; + fakeGlobalFilter.push(new FilterModel('FakeInvolvedTasks', false, 'glyphicon-align-left', '', 'open', 'fake-involved')); + fakeGlobalFilter.push(new FilterModel('FakeMyTasks', false, 'glyphicon-align-left', '', 'open', 'fake-assignee')); + + let fakeGlobalFilterPromise = new Promise(function (resolve, reject) { + resolve(fakeGlobalFilter); + }); + + let fakeErrorFilterList = { + error: 'wrong request' + }; + + let fakeErrorFilterPromise = new Promise(function (resolve, reject) { + reject(fakeErrorFilterList); + }); + + beforeEach(() => { + let activitiService = new ActivitiTaskListService(null); + filterList = new ActivitiFilters(null, null, activitiService); + }); + + it('should return the filter task list', (done) => { + spyOn(filterList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise)); + + filterList.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(filterList.filters).toBeDefined(); + expect(filterList.filters.length).toEqual(2); + expect(filterList.filters[0].name).toEqual('FakeInvolvedTasks'); + expect(filterList.filters[1].name).toEqual('FakeMyTasks'); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should return the filter task list, filtered By Name', (done) => { + + let fakeDeployedApplicationsPromise = new Promise(function (resolve, reject) { + resolve({}); + }); + + spyOn(filterList.activiti, 'getDeployedApplications').and.returnValue(Observable.fromPromise(fakeDeployedApplicationsPromise)); + spyOn(filterList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise)); + + filterList.appName = 'test'; + + filterList.onSuccess.subscribe((res) => { + let deployApp: any = filterList.activiti.getDeployedApplications; + expect(deployApp.calls.count()).toEqual(1); + expect(res).toBeDefined(); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should emit an error with a bad response', (done) => { + spyOn(filterList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise)); + + filterList.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should emit an event when a filter is selected', (done) => { + let currentFilter = new FilterModel('FakeInvolvedTasks', false, 'glyphicon-align-left', '', 'open', 'fake-involved'); + + filterList.filterClick.subscribe((filter: FilterModel) => { + expect(filter).toBeDefined(); + expect(filter).toEqual(currentFilter); + expect(filterList.currentFilter).toEqual(currentFilter); + done(); + }); + + filterList.selectFilter(currentFilter); + }); + +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.ts new file mode 100644 index 0000000000..9dff5f1289 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.ts @@ -0,0 +1,131 @@ +/*! + * @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, Output, EventEmitter, OnInit, Input } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { FilterModel } from '../models/filter.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-filters', + moduleId: __moduleName, + templateUrl: './activiti-filters.component.html', + styleUrls: ['activiti-filters.component.css'], + providers: [ActivitiTaskListService], + pipes: [AlfrescoPipeTranslate] + +}) +export class ActivitiFilters implements OnInit { + + @Output() + filterClick: EventEmitter = new EventEmitter(); + + @Output() + onSuccess: EventEmitter = new EventEmitter(); + + @Output() + onError: EventEmitter = new EventEmitter(); + + @Input() + appId: string; + + @Input() + appName: string; + + private filterObserver: Observer; + filter$: Observable; + + currentFilter: FilterModel; + + filters: FilterModel [] = []; + + /** + * Constructor + * @param auth + * @param translate + * @param activiti + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + public activiti: ActivitiTaskListService) { + this.filter$ = new Observable(observer => this.filterObserver = observer).share(); + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + } + + ngOnInit() { + this.filter$.subscribe((filter: FilterModel) => { + this.filters.push(filter); + }); + + this.load(); + } + + /** + * The method call the adapter data table component for render the task list + * @param tasks + */ + private load() { + if (this.appName) { + this.filterByAppName(); + } else { + this.filterByAppId(this.appId); + } + } + + private filterByAppId(appId) { + this.activiti.getTaskListFilters(appId).subscribe( + (res: FilterModel[]) => { + res.forEach((filter) => { + this.filterObserver.next(filter); + }); + this.onSuccess.emit(res); + }, + (err) => { + console.log(err); + this.onError.emit(err); + } + ); + } + + private filterByAppName() { + this.activiti.getDeployedApplications(this.appName).subscribe( + application => { + this.filterByAppId(application.id); + }, + (err) => { + console.log(err); + this.onError.emit(err); + }); + } + + /** + * Pass the selected filter as next + * @param filter + */ + public selectFilter(filter: FilterModel) { + this.currentFilter = filter; + this.filterClick.emit(filter); + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css new file mode 100644 index 0000000000..7e9e64291f --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css @@ -0,0 +1,11 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} + +.material-icons:hover { + color: rgb(255, 152, 0); +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html new file mode 100644 index 0000000000..5430f9b172 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html @@ -0,0 +1,33 @@ +{{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }} +
    add
    +
    + Add a person +
    + +
    + {{ 'TASK_DETAILS.PEOPLE.NONE' | translate }} +
    + + +

    New User

    +
    +
    + + +
    +
    +
    + + +
    +
    diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts new file mode 100644 index 0000000000..9509ff717d --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts @@ -0,0 +1,84 @@ +/*! + * @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, Input, OnInit, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { User } from '../models/user.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-people', + moduleId: __moduleName, + templateUrl: './activiti-people.component.html', + styleUrls: ['./activiti-people.component.css'], + pipes: [ AlfrescoPipeTranslate ] + +}) +export class ActivitiPeople implements OnInit { + + @Input() + people: User [] = []; + + @ViewChild('dialog') + dialog: any; + + private peopleObserver: Observer; + people$: Observable; + + /** + * Constructor + * @param auth + * @param translate + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + this.people$ = new Observable(observer => this.peopleObserver = observer).share(); + } + + ngOnInit() { + this.people$.subscribe((user: User) => { + this.people.push(user); + }); + } + + public showDialog() { + if (this.dialog) { + this.dialog.nativeElement.showModal(); + } + } + + public add() { + alert('add people'); + + this.cancel(); + } + + public cancel() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } + +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.css new file mode 100644 index 0000000000..07eaf92d80 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.css @@ -0,0 +1,3 @@ +:host { + width: 100%; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.html new file mode 100644 index 0000000000..ef26457a86 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.html @@ -0,0 +1,27 @@ +
    {{ 'TASK_DETAILS.MESSAGES.NONE' | translate }}
    +
    +

    {{taskDetails.name}}

    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + + +
    diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.ts new file mode 100644 index 0000000000..00364a67d0 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.ts @@ -0,0 +1,150 @@ +/*! + * @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, Input, OnInit, ViewChild, Output, EventEmitter } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { ActivitiTaskHeader } from './activiti-task-header.component'; +import { ActivitiComments } from './activiti-comments.component'; +import { ActivitiChecklist } from './activiti-checklist.component'; +import { ActivitiPeople } from './activiti-people.component'; +import { TaskDetailsModel } from '../models/task-details.model'; +import { User } from '../models/user.model'; +import { ActivitiForm, FormModel, FormService } from 'ng2-activiti-form'; + + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-task-details', + moduleId: __moduleName, + templateUrl: './activiti-task-details.component.html', + styleUrls: ['./activiti-task-details.component.css'], + providers: [ActivitiTaskListService, FormService], + directives: [ActivitiTaskHeader, ActivitiPeople, ActivitiComments, ActivitiChecklist, ActivitiForm], + pipes: [AlfrescoPipeTranslate] + +}) +export class ActivitiTaskDetails implements OnInit { + + @Input() + taskId: string; + + @ViewChild('activiticomments') + activiticomments: any; + + @ViewChild('activitichecklist') + activitichecklist: any; + + @Input() + showTitle: boolean = true; + + @Input() + showCompleteButton: boolean = true; + + @Input() + showSaveButton: boolean = true; + + @Input() + readOnly: boolean = false; + + @Input() + showRefreshButton: boolean = true; + + @Output() + formSaved = new EventEmitter(); + + @Output() + formCompleted = new EventEmitter(); + + @Output() + formLoaded = new EventEmitter(); + + taskDetails: TaskDetailsModel; + + taskForm: FormModel; + + taskPeople: User[] = []; + + /** + * Constructor + * @param auth + * @param translate + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + private activitiForm: FormService, + private activitiTaskList: ActivitiTaskListService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + } + + ngOnInit() { + if (this.taskId) { + this.loadDetails(this.taskId); + } + } + + loadDetails(taskId: string) { + this.taskForm = null; + this.taskPeople = []; + if (taskId) { + this.activitiTaskList.getTaskDetails(taskId).subscribe( + (res: TaskDetailsModel) => { + this.taskDetails = res; + if (this.taskDetails && this.taskDetails.involvedPeople) { + this.taskDetails.involvedPeople.forEach((user) => { + this.taskPeople.push(new User(user.id, user.email, user.firstName, user.lastName)); + }); + + if (this.activiticomments) { + this.activiticomments.load(this.taskDetails.id); + } + + if (this.activitichecklist) { + this.activitichecklist.load(this.taskDetails.id); + } + } + console.log(this.taskDetails); + } + ); + } + } + + onComplete() { + this.activitiTaskList.completeTask(this.taskId).subscribe( + (res) => { + console.log(res); + } + ); + } + + formSavedEmitter(data: any) { + this.formSaved.emit(data); + } + + formCompletedEmitter(data: any) { + this.formCompleted.emit(data); + } + + formLoadedEmitter(data: any) { + this.formLoaded.emit(data); + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.css new file mode 100644 index 0000000000..20fbab0626 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.css @@ -0,0 +1,7 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.html new file mode 100644 index 0000000000..c098c1d8c3 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.html @@ -0,0 +1,18 @@ +
    +
    +
    + {{ 'TASK_DETAILS.LABELS.ASSIGNEE' | translate }}: + {{taskDetails.assignee.lastName }} +
    +
    + + {{ 'TASK_DETAILS.LABELS.DUE' | translate }}: + {{taskDetails?.dueDate ? taskDetails.dueDate : ('TASK_DETAILS.DUE.NONE' |translate) }} + +
    +
    + {{ 'TASK_DETAILS.LABELS.FORM' | translate }}: + {{taskForm?.name ? taskForm.name : ('TASK_DETAILS.FORM.NONE' | translate) }} +
    +
    +
    \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.ts new file mode 100644 index 0000000000..2c87a8845f --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.ts @@ -0,0 +1,82 @@ +/*! + * @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, Input, OnInit, OnChanges } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { TaskDetailsModel } from '../models/task-details.model'; +import { FormModel, FormService } from 'ng2-activiti-form'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-task-header', + moduleId: __moduleName, + templateUrl: './activiti-task-header.component.html', + styleUrls: ['./activiti-task-header.component.css'], + providers: [ FormService ], + pipes: [ AlfrescoPipeTranslate ] + +}) +export class ActivitiTaskHeader implements OnInit, OnChanges { + + @Input() + taskDetails: TaskDetailsModel; + + taskForm: FormModel; + + /** + * Constructor + * @param auth + * @param translate + */ + constructor(private auth: AlfrescoAuthenticationService, + private activitiForm: FormService, + private translate: AlfrescoTranslationService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + } + + ngOnInit() { + if (this.taskDetails && this.taskDetails.formKey) { + this.load(this.taskDetails.id); + } + } + + ngOnChanges(change) { + if (this.taskDetails && this.taskDetails.formKey) { + this.load(this.taskDetails.id); + } else { + this.taskForm = null; + } + } + + public load(taskId: string) { + if (taskId) { + this.activitiForm.getTaskForm(taskId).subscribe( + (response) => { + this.taskForm = response; + }, + (err) => { + console.error(err); + } + ); + } + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.css new file mode 100644 index 0000000000..58224391d9 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.css @@ -0,0 +1,3 @@ +alfresco-datatable >>> .data-cell { + cursor: pointer; +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.html new file mode 100644 index 0000000000..4f932db173 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.html @@ -0,0 +1,12 @@ +
    {{ 'TASK_FILTERS.MESSAGES.NONE' | translate }}
    +
    +
    + + +
    +
    + {{ 'TASK_LIST.MESSAGES.NONE' | translate }} +
    +
    diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.spec.ts new file mode 100644 index 0000000000..b8374faf83 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.spec.ts @@ -0,0 +1,136 @@ +/*! + * @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 { + it, + describe, + expect, + beforeEach +} from '@angular/core/testing'; + +import { ActivitiTaskList } from './activiti-tasklist.component'; +import { ActivitiTaskListService } from '../services/activiti-tasklist.service'; +import { FilterModel } from '../models/filter.model'; +import { Observable } from 'rxjs/Rx'; +import { ObjectDataRow, DataRowEvent } from 'ng2-alfresco-datatable'; + + +describe('ActivitiTaskList', () => { + + let taskList: ActivitiTaskList; + + let fakeGlobalTask = { + size: 1, total: 12, start: 0, + data: [ + { + id: 14, name: 'fake-long-name-fake-long-name-fake-long-name-fak50-long-name', description: null, category: null, + assignee: { + id: 1, firstName: null, lastName: 'Administrator', email: 'admin' + } + }, + { + id: 2, name: '', description: null, category: null, + assignee: { + id: 1, firstName: null, lastName: 'Administrator', email: 'admin' + } + } + ] + }; + + let fakeGlobalTaskPromise = new Promise(function (resolve, reject) { + resolve(fakeGlobalTask); + }); + + let fakeErrorTaskList = { + error: 'wrong request' + }; + + let fakeErrorTaskPromise = new Promise(function (resolve, reject) { + reject(fakeErrorTaskList); + }); + + beforeEach(() => { + let activitiSerevice = new ActivitiTaskListService(null); + taskList = new ActivitiTaskList(null, null, activitiSerevice); + }); + + it('should use the default schemaColumn as default', () => { + taskList.ngOnInit(); + expect(taskList.schemaColumn).toBeDefined(); + expect(taskList.schemaColumn.length).toEqual(4); + }); + + it('should use the schemaColumn passed in input', () => { + taskList.schemaColumn = [ + {type: 'text', key: 'fake-id', title: 'Name'} + ]; + + taskList.ngOnInit(); + expect(taskList.schemaColumn).toBeDefined(); + expect(taskList.schemaColumn.length).toEqual(1); + }); + + it('should return an empty task list when the taskFilter is not passed', () => { + taskList.ngOnInit(); + expect(taskList.tasks).toBeUndefined(); + expect(taskList.isTaskListEmpty()).toBeTruthy(); + }); + + it('should return the filtered task list when the taskFilter is passed', (done) => { + spyOn(taskList.activiti, 'getTasks').and.returnValue(Observable.fromPromise(fakeGlobalTaskPromise)); + taskList.taskFilter = new FilterModel('name', false, 'icon', '', 'open', 'fake-assignee'); + + taskList.onSuccess.subscribe( (res) => { + expect(res).toBeDefined(); + expect(taskList.tasks).toBeDefined(); + expect(taskList.isTaskListEmpty()).not.toBeTruthy(); + expect(taskList.tasks.getRows().length).toEqual(2); + expect(taskList.tasks.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(taskList.tasks.getRows()[1].getValue('name')).toEqual('Nameless task'); + done(); + }); + + taskList.ngOnInit(); + }); + + it('should throw an exception when the response is wrong', (done) => { + spyOn(taskList.activiti, 'getTasks').and.returnValue(Observable.fromPromise(fakeErrorTaskPromise)); + taskList.taskFilter = new FilterModel('name', false, 'icon', '', 'open', 'fake-assignee'); + + taskList.onError.subscribe( (err) => { + expect(err).toBeDefined(); + done(); + }); + + taskList.ngOnInit(); + }); + + it('should emit row click event', (done) => { + let row = new ObjectDataRow({ + id: 999 + }); + let rowEvent = {value: row}; + + taskList.rowClick.subscribe(taskId => { + expect(taskId).toEqual(999); + done(); + }); + + taskList.onRowClick(rowEvent); + }); + +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.ts new file mode 100644 index 0000000000..848c215aa3 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.ts @@ -0,0 +1,144 @@ +/*! + * @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, Input, Output, EventEmitter, OnInit } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter, DataTableAdapter, DataRowEvent } from 'ng2-alfresco-datatable'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { FilterModel } from '../models/filter.model'; + +declare let componentHandler: any; +declare let __moduleName: string; + +@Component({ + selector: 'activiti-tasklist', + moduleId: __moduleName, + templateUrl: './activiti-tasklist.component.html', + styleUrls: ['./activiti-tasklist.component.css'], + directives: [ALFRESCO_DATATABLE_DIRECTIVES], + providers: [ActivitiTaskListService], + pipes: [AlfrescoPipeTranslate] + +}) +export class ActivitiTaskList implements OnInit { + + @Input() + taskFilter: FilterModel; + + @Input() + schemaColumn: any[] = [ + {type: 'text', key: 'id', title: 'Id'}, + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'formKey', title: 'Form Key', sortable: true}, + {type: 'text', key: 'created', title: 'Created', sortable: true} + ]; + + @Output() + rowClick: EventEmitter = new EventEmitter(); + + @Output() + onSuccess: EventEmitter = new EventEmitter(); + + @Output() + onError: EventEmitter = new EventEmitter(); + + data: DataTableAdapter; + + tasks: ObjectDataTableAdapter; + + currentTaskId: string; + + /** + * Constructor + * @param auth + * @param translate + * @param translate + */ + constructor(private auth: AlfrescoAuthenticationService, + private translate: AlfrescoTranslationService, + public activiti: ActivitiTaskListService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + } + + ngOnInit() { + this.data = new ObjectDataTableAdapter( + [], + this.schemaColumn + ); + + if (this.taskFilter) { + this.load(this.taskFilter); + } + } + + public load(filter: FilterModel) { + this.activiti.getTasks(filter).subscribe( + (res) => { + this.renderTasks(res.data); + this.onSuccess.emit(res); + }, (err) => { + console.error(err); + this.onError.emit(err); + }); + } + + /** + * The method call the adapter data table component for render the task list + * @param tasks + */ + private renderTasks(tasks: any[]) { + tasks = this.optimizeTaskName(tasks); + this.tasks = new ObjectDataTableAdapter(tasks, this.data.getColumns()); + } + + /** + * Check if the tasks list is empty + * @returns {ObjectDataTableAdapter|boolean} + */ + isTaskListEmpty(): boolean { + return this.tasks === undefined || (this.tasks && this.tasks.getRows() && this.tasks.getRows().length === 0); + } + + /** + * Emit the event rowClick passing the current task id when the row is clicked + * @param event + */ + onRowClick(event: DataRowEvent) { + let item = event; + this.currentTaskId = item.value.getValue('id'); + this.rowClick.emit(this.currentTaskId); + } + + /** + * Optimize task name field + * @param tasks + * @returns {any[]} + */ + private optimizeTaskName(tasks: any[]) { + tasks = tasks.map(t => { + t.name = t.name || 'Nameless task'; + if (t.name.length > 50) { + t.name = t.name.substring(0, 50) + '...'; + } + return t; + }); + return tasks; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/i18n/en.json b/ng2-components/ng2-activiti-tasklist/src/i18n/en.json new file mode 100644 index 0000000000..d83089f9f6 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/i18n/en.json @@ -0,0 +1,40 @@ +{ + "TASK_LIST": { + "MESSAGES": { + "NONE": "No tasks list found." + } + }, + "TASK_DETAILS": { + "LABELS": { + "ASSIGNEE": "Assignee", + "DUE": "Due", + "FORM": "Form", + "PEOPLE": "People", + "COMMENTS": "Comments", + "CHECKLIST": "Checklist" + }, + "MESSAGES": { + "NONE": "No task details found." + }, + "FORM": { + "NONE": "No form." + }, + "DUE": { + "NONE": "No due date." + }, + "PEOPLE": { + "NONE": "No people involved." + }, + "COMMENTS": { + "NONE": "No comments." + }, + "CHECKLIST": { + "NONE": "No checklist." + } + }, + "TASK_FILTERS": { + "MESSAGES": { + "NONE": "No task filter selected." + } + } +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/i18n/it.json b/ng2-components/ng2-activiti-tasklist/src/i18n/it.json new file mode 100644 index 0000000000..62021c250d --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/i18n/it.json @@ -0,0 +1,28 @@ +{ + "TASK_LIST": { + "MESSAGES": { + "NONE": "Nessuna lista tasks trovata." + } + }, + "TASK_DETAILS": { + "LABELS": { + "ASSIGNEE": "Assegnatario", + "DUE": "Scadenza", + "FORM": "Form" + }, + "MESSAGES": { + "NONE": "Nessun dettaglio task trovato." + }, + "FORM": { + "NONE": "Nessuna form." + }, + "DUE": { + "NONE": "Nessuna data di scadenza." + } + }, + "TASK_FILTERS": { + "MESSAGES": { + "NONE": "Nessun filtro task selezionato." + } + } +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/models/comment.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/comment.model.ts new file mode 100644 index 0000000000..ba7ab40c86 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/models/comment.model.ts @@ -0,0 +1,39 @@ +/*! + * @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. + */ + +/** + * + * This object represent the comment of a task. + * + * + * @returns {Comment} . + */ +import { User } from './user.model'; + +export class Comment { + id: number; + message: string; + created: string; + createdBy: User; + + constructor(id: number, message: string, created: string, createdBy: User) { + this.id = id; + this.message = message; + this.created = created; + this.createdBy = createdBy; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/models/filter.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/filter.model.ts new file mode 100644 index 0000000000..22acd2523b --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/models/filter.model.ts @@ -0,0 +1,59 @@ +/*! + * @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. + */ + +/** + * + * This object represent the filter. + * + * + * @returns {FilterModel} . + */ +export class FilterModel { + id: number; + name: string; + recent: boolean = false; + icon: string; + filter: FilterParamsModel; + + constructor(name: string, recent: boolean, icon: string, query: string, state: string, assignment: string, appDefinitionId?: string) { + this.name = name; + this.recent = recent; + this.icon = icon; + this.filter = new FilterParamsModel(query, state, assignment, appDefinitionId); + } +} + +/** + * + * This object represent the parameters of a filter. + * + * + * @returns {FilterModel} . + */ +export class FilterParamsModel { + name: string; + state: string; + assignment: string; + appDefinitionId: string; + + constructor(query: string, state: string, assignment: string, appDefinitionId?: string) { + this.name = query; + this.state = state; + this.assignment = assignment; + this.appDefinitionId = appDefinitionId; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts new file mode 100644 index 0000000000..5ab0361aaa --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts @@ -0,0 +1,93 @@ +/*! + * @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. + */ + +/** + * + * This object represent the details of a task. + * + * + * @returns {TaskDetailsModel} . + */ +import { User } from './user.model'; + +export class TaskDetailsModel { + id: string; + name: string; + assignee: User; + priority: number; + adhocTaskCanBeReassigned: number; + category: string; + created: string; + description: string; + dueDate: string; + duration: string; + endDate: string; + executionId: string; + formKey: string; + initiatorCanCompleteTask: boolean = false; + managerOfCandidateGroup: boolean = false; + memberOfCandidateGroup: boolean = false; + memberOfCandidateUsers: boolean = false; + involvedPeople: User []; + parentTaskId: string; + parentTaskName: string; + processDefinitionCategory: string; + processDefinitionDeploymentId: string; + processDefinitionDescription: string; + processDefinitionId: string; + processDefinitionKey: string; + processDefinitionName: string; + processDefinitionVersion: number = 0; + processInstanceId: string; + processInstanceName: string; + processInstanceStartUserId: string; + taskDefinitionKey: string; + + + constructor(obj: any) { + this.id = obj.id; + this.name = obj.name; + this.priority = obj.priority; + this.assignee = new User(obj.assignee.id, obj.assignee.email, obj.assignee.firstName, obj.assignee.lastName); + this.adhocTaskCanBeReassigned = obj.adhocTaskCanBeReassigned; + this.created = obj.created; + this.description = obj.description; + this.dueDate = obj.dueDate; + this.duration = obj.duration; + this.endDate = obj.endDate; + this.executionId = obj.executionId; + this.formKey = obj.formKey; + this.initiatorCanCompleteTask = obj.initiatorCanCompleteTask; + this.managerOfCandidateGroup = obj.managerOfCandidateGroup; + this.memberOfCandidateGroup = obj.memberOfCandidateGroup; + this.memberOfCandidateUsers = obj.memberOfCandidateUsers; + this.involvedPeople = obj.involvedPeople; + this.parentTaskId = obj.parentTaskId; + this.parentTaskName = obj.parentTaskName; + this.processDefinitionCategory = obj.processDefinitionCategory; + this.processDefinitionDeploymentId = obj.processDefinitionDeploymentId; + this.processDefinitionDescription = obj.processDefinitionDescription; + this.processDefinitionId = obj.processDefinitionId; + this.processDefinitionKey = obj.processDefinitionKey; + this.processDefinitionName = obj.processDefinitionName; + this.processDefinitionVersion = obj.processDefinitionVersion; + this.processInstanceId = obj.processInstanceId; + this.processInstanceName = obj.processInstanceName; + this.processInstanceStartUserId = obj.processInstanceStartUserId; + this.taskDefinitionKey = obj.taskDefinitionKey; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/models/user.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/user.model.ts new file mode 100644 index 0000000000..6d21ad4e0b --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/models/user.model.ts @@ -0,0 +1,38 @@ +/*! + * @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. + */ + +/** + * + * This object represent the user. + * + * + * @returns {User} . + */ + +export class User { + id: number; + email: string; + firstName: string; + lastName: string; + + constructor(id: number, email: string, firstName: string, lastName: string) { + this.id = id; + this.email = email; + this.firstName = firstName; + this.lastName = lastName; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.spec.ts b/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.spec.ts new file mode 100644 index 0000000000..3c9eafefc8 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.spec.ts @@ -0,0 +1,305 @@ +/*! + * @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 {it, describe, inject, beforeEach, beforeEachProviders} from '@angular/core/testing'; +import {ActivitiTaskListService} from './activiti-tasklist.service'; +import {AlfrescoSettingsService, AlfrescoAuthenticationService} from 'ng2-alfresco-core'; +import {TaskDetailsModel} from '../models/task-details.model'; +import {Comment} from '../models/comment.model'; + +declare let AlfrescoApi: any; +declare let jasmine: any; + +describe('ActivitiTaskListService', () => { + let service: any; + + let fakeFilters = { + size: 2, total: 2, start: 0, + data: [ + { + id: 1, name: 'FakeInvolvedTasks', recent: false, icon: 'glyphicon-align-left', + filter: {sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved'} + }, + { + id: 2, name: 'FakeMyTasks', recent: false, icon: 'glyphicon-align-left', + filter: {sort: 'created-desc', name: '', state: 'open', assignment: 'fake-assignee'} + } + ] + }; + + let fakeFilter = { + page: 2, filterId: 2, appDefinitionId: null, + filter: {sort: 'created-desc', name: '', state: 'open', assignment: 'fake-assignee'} + }; + + let fakeUser = {id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName'}; + + let fakeTaskList = { + size: 1, total: 1, start: 0, + data: [ + { + id: 1, name: 'FakeNameTask', description: null, category: null, + assignee: fakeUser, + created: '2016-07-15T11:19:17.440+0000' + } + ] + }; + + let fakeErrorTaskList = { + error: 'wrong request' + }; + + let fakeTaskDetails = {id: '999', name: 'fake-task-name', formKey: '99', assignee: fakeUser}; + + let fakeTasksComment = { + size: 2, total: 2, start: 0, + data: [ + { + id: 1, message: 'fake-message-1', created: '', createdBy: fakeUser + }, + { + id: 2, message: 'fake-message-2', created: '', createdBy: fakeUser + } + ] + }; + + let fakeTasksChecklist = { + size: 1, total: 1, start: 0, + data: [ + { + id: 1, name: 'FakeCheckTask1', description: null, category: null, + assignee: fakeUser, + created: '2016-07-15T11:19:17.440+0000' + }, + { + id: 2, name: 'FakeCheckTask2', description: null, category: null, + assignee: fakeUser, + created: '2016-07-15T11:19:17.440+0000' + } + ] + }; + + beforeEachProviders(() => { + return [ + ActivitiTaskListService, + AlfrescoSettingsService, + AlfrescoAuthenticationService + ]; + }); + + beforeEach(inject([ActivitiTaskListService], (activitiTaskListService: ActivitiTaskListService) => { + jasmine.Ajax.install(); + service = activitiTaskListService; + })); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should return the task list filters', (done) => { + service.getTaskListFilters().subscribe( + (res) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].name).toEqual('FakeInvolvedTasks'); + expect(res[1].name).toEqual('FakeMyTasks'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeFilters) + }); + }); + + it('should return the task list filtered', (done) => { + service.getTasks(fakeFilter).subscribe( + res => { + expect(res).toBeDefined(); + expect(res.size).toEqual(1); + expect(res.total).toEqual(1); + expect(res.data.length).toEqual(1); + expect(res.data[0].name).toEqual('FakeNameTask'); + expect(res.data[0].assignee.email).toEqual('fake-email@dom.com'); + expect(res.data[0].assignee.firstName).toEqual('firstName'); + expect(res.data[0].assignee.lastName).toEqual('lastName'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskList) + }); + }); + + it('should throw an exception when the response is wrong', () => { + service.getTasks(fakeFilter).subscribe( + (res) => { + }, + (err: any) => { + expect(err).toBeDefined(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 404, + contentType: 'application/json', + responseText: JSON.stringify(fakeErrorTaskList) + }); + }); + + it('should return the task details ', (done) => { + service.getTaskDetails(999).subscribe( + (res: TaskDetailsModel) => { + expect(res).toBeDefined(); + expect(res.id).toEqual('999'); + expect(res.name).toEqual('fake-task-name'); + expect(res.formKey).toEqual('99'); + expect(res.assignee).toBeDefined(); + expect(res.assignee.email).toEqual('fake-email@dom.com'); + expect(res.assignee.firstName).toEqual('firstName'); + expect(res.assignee.lastName).toEqual('lastName'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskDetails) + }); + }); + + it('should return the tasks comments ', (done) => { + service.getTaskComments(999).subscribe( + (res: Comment[]) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].message).toEqual('fake-message-1'); + expect(res[1].message).toEqual('fake-message-2'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTasksComment) + }); + }); + + it('should return the tasks checklists ', (done) => { + service.getTaskChecklist(999).subscribe( + (res: TaskDetailsModel[]) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].name).toEqual('FakeCheckTask1'); + expect(res[0].assignee.email).toEqual('fake-email@dom.com'); + expect(res[0].assignee.firstName).toEqual('firstName'); + expect(res[0].assignee.lastName).toEqual('lastName'); + expect(res[1].name).toEqual('FakeCheckTask2'); + expect(res[1].assignee.email).toEqual('fake-email@dom.com'); + expect(res[1].assignee.firstName).toEqual('firstName'); + expect(res[0].assignee.lastName).toEqual('lastName'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTasksChecklist) + }); + }); + + it('should add a task ', (done) => { + let taskFake = new TaskDetailsModel({ + id: 123, + parentTaskId: 456, + name: 'FakeNameTask', + description: null, + category: null, + assignee: fakeUser, + created: '' + }); + + service.addTask(taskFake).subscribe( + (res: TaskDetailsModel) => { + expect(res).toBeDefined(); + expect(res.id).not.toEqual(''); + expect(res.name).toEqual('FakeNameTask'); + expect(res.created).not.toEqual(''); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({ + id: '777', name: 'FakeNameTask', description: null, category: null, + assignee: fakeUser, + created: '2016-07-15T11:19:17.440+0000' + }) + }); + }); + + it('should add a comment task ', (done) => { + service.addTaskComment(999, 'fake-comment-message').subscribe( + (res: Comment) => { + expect(res).toBeDefined(); + expect(res.id).not.toEqual(''); + expect(res.message).toEqual('fake-comment-message'); + expect(res.created).not.toEqual(''); + expect(res.createdBy.email).toEqual('fake-email@dom.com'); + expect(res.createdBy.firstName).toEqual('firstName'); + expect(res.createdBy.lastName).toEqual('lastName'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({ + id: '111', message: 'fake-comment-message', + createdBy: fakeUser, + created: '2016-07-15T11:19:17.440+0000' + }) + }); + }); + + it('should complete the task ', (done) => { + service.completeTask(999).subscribe( + (res: any) => { + expect(res).toBeDefined(); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.ts b/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.ts new file mode 100644 index 0000000000..5b9fb5afb0 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.ts @@ -0,0 +1,199 @@ +/*! + * @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 {Injectable} from '@angular/core'; +import {AlfrescoAuthenticationService} from 'ng2-alfresco-core'; +import {Observable} from 'rxjs/Rx'; +import {FilterModel} from '../models/filter.model'; +import {FilterParamsModel} from '../models/filter.model'; +import {Comment} from '../models/comment.model'; +import {User} from '../models/user.model'; +import {TaskDetailsModel} from '../models/task-details.model'; + +@Injectable() +export class ActivitiTaskListService { + + constructor(public authService: AlfrescoAuthenticationService) { + } + + /** + * Retrive all the Deployed app + * @returns {Observable} + */ + getDeployedApplications(name: string): Observable { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.appsApi.getAppDefinitions()) + .map((response: any) => response.data.find(p => p.name === name)) + .do(data => console.log('Application: ' + JSON.stringify(data))); + } + + /** + * Retrive all the Tasks filters + * @returns {Observable} + */ + getTaskListFilters(appId?: string): Observable { + return Observable.fromPromise(this.callApiTaskFilters(appId)) + .map((response: any) => { + let filters: FilterModel[] = []; + response.data.forEach((filter) => { + let filterModel = new FilterModel(filter.name, filter.recent, filter.icon, + filter.filter.name, filter.filter.state, filter.filter.assignment, appId); + filters.push(filterModel); + }); + return filters; + }).catch(this.handleError); + } + + /** + * Retrive all the tasks filtered by filterModel + * @param filter - FilterModel + * @returns {any} + */ + getTasks(filter: FilterModel): Observable { + return Observable.fromPromise(this.callApiTasksFiltered(filter.filter)) + .map((res: any) => { + return res; + }).catch(this.handleError); + } + + /** + * Retrive all the task details + * @param id - taskId + * @returns {} + */ + getTaskDetails(id: string): Observable { + return Observable.fromPromise(this.callApiTaskDetails(id)) + .map(res => res) + .map((details: any) => { + return new TaskDetailsModel(details); + }).catch(this.handleError); + } + + /** + * Retrive all the task's comments + * @param id - taskId + * @returns {} + */ + getTaskComments(id: string): Observable { + return Observable.fromPromise(this.callApiTaskComments(id)) + .map(res => res) + .map((response: any) => { + let comments: Comment[] = []; + response.data.forEach((comment) => { + let user = new User( + comment.createdBy.id, comment.createdBy.email, comment.createdBy.firstName, comment.createdBy.lastName); + comments.push(new Comment(comment.id, comment.message, comment.created, user)); + }); + return comments; + }).catch(this.handleError); + } + + /** + * Retrive all the task's checklist + * @param id - taskId + * @returns {TaskDetailsModel} + */ + getTaskChecklist(id: string): Observable { + return Observable.fromPromise(this.callApiTaskChecklist(id)) + .map(res => res) + .map((response: any) => { + let checklists: TaskDetailsModel[] = []; + response.data.forEach((checklist) => { + checklists.push(new TaskDetailsModel(checklist)); + }); + return checklists; + }).catch(this.handleError); + } + + /** + * Add a task + * @param task - TaskDetailsModel + * @returns {TaskDetailsModel} + */ + addTask(task: TaskDetailsModel): Observable { + return Observable.fromPromise(this.callApiAddTask(task)) + .map(res => res) + .map((response: TaskDetailsModel) => { + return new TaskDetailsModel(response); + }).catch(this.handleError); + } + + /** + * Add a comment to a task + * @param id - taskId + * @param message - content of the comment + * @returns {Comment} + */ + addTaskComment(id: string, message: string): Observable { + return Observable.fromPromise(this.callApiAddTaskComment(id, message)) + .map(res => res) + .map((response: Comment) => { + return new Comment(response.id, response.message, response.created, response.createdBy); + }).catch(this.handleError); + + } + + /** + * Make the task completed + * @param id - taskId + * @returns {TaskDetailsModel} + */ + completeTask(id: string) { + return Observable.fromPromise(this.callApiCompleteTask(id)) + .map(res => res); + } + + private callApiTasksFiltered(filter: FilterParamsModel) { + return this.authService.getAlfrescoApi().activiti.taskApi.listTasks(filter); + } + + private callApiTaskFilters(appId?: string) { + if (appId) { + return this.authService.getAlfrescoApi().activiti.userFiltersApi.getUserTaskFilters({appId: appId}); + } else { + return this.authService.getAlfrescoApi().activiti.userFiltersApi.getUserTaskFilters(); + } + } + + private callApiTaskDetails(id: string) { + return this.authService.getAlfrescoApi().activiti.taskApi.getTask(id); + } + + private callApiTaskComments(id: string) { + return this.authService.getAlfrescoApi().activiti.taskApi.getTaskComments(id); + } + + private callApiAddTaskComment(id: string, message: string) { + return this.authService.getAlfrescoApi().activiti.taskApi.addTaskComment({message: message}, id); + } + + private callApiAddTask(task: TaskDetailsModel) { + return this.authService.getAlfrescoApi().activiti.taskApi.addSubtask(task.parentTaskId, task); + } + + private callApiTaskChecklist(id: string) { + return this.authService.getAlfrescoApi().activiti.taskApi.getChecklist(id); + } + + private callApiCompleteTask(id: string) { + return this.authService.getAlfrescoApi().activiti.taskApi.completeTask(id); + } + + private handleError(error: any) { + console.error(error); + return Observable.throw(error || 'Server error'); + } +} diff --git a/ng2-components/ng2-activiti-tasklist/tsconfig.json b/ng2-components/ng2-activiti-tasklist/tsconfig.json new file mode 100644 index 0000000000..39deca0930 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "removeComments": true, + "declaration": true, + "noLib": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": false, + "noImplicitReturns": false, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "outDir": "dist" + }, + "exclude": [ + "demo", + "node_modules", + "typings/main", + "typings/main.d.ts", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-tasklist/tslint.json b/ng2-components/ng2-activiti-tasklist/tslint.json new file mode 100644 index 0000000000..85e9df53c1 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/tslint.json @@ -0,0 +1,121 @@ +{ + "rules": { + "align": [ + true, + "parameters", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "interface-name": false, + "jsdoc-format": true, + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 180 + ], + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-any": false, + "no-arg": true, + "no-bitwise": false, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-constructor-vars": false, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unreachable": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single", + "avoid-escape" + ], + "radix": true, + "semicolon": true, + "switch-default": true, + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef": false, + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "use-strict": false, + "variable-name": [ + true, + "check-format", + "allow-leading-underscore", + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-operator", + "check-separator", + "check-type", + "check-module", + "check-decl" + ] + } +} diff --git a/ng2-components/ng2-activiti-tasklist/typings.json b/ng2-components/ng2-activiti-tasklist/typings.json new file mode 100644 index 0000000000..d8954c2485 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/typings.json @@ -0,0 +1,7 @@ +{ + "globalDependencies": { + "core-js": "registry:dt/core-js#0.0.0+20160317120654", + "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", + "node": "registry:dt/node#4.0.0+20160509154515" + } +} diff --git a/ng2-components/ng2-alfresco-core/.gitignore b/ng2-components/ng2-alfresco-core/.gitignore index bf980cac93..13324e66b9 100644 --- a/ng2-components/ng2-alfresco-core/.gitignore +++ b/ng2-components/ng2-alfresco-core/.gitignore @@ -11,5 +11,5 @@ demo/**/*.js demo/**/*.js.map demo/**/*.d.ts index.js -index.map +index.js.map !systemjs.config.js diff --git a/ng2-components/ng2-alfresco-core/README.md b/ng2-components/ng2-alfresco-core/README.md index 06a1ef51d6..078df01b91 100644 --- a/ng2-components/ng2-alfresco-core/README.md +++ b/ng2-components/ng2-alfresco-core/README.md @@ -1,12 +1,16 @@ # Alfresco Angular2 Components core

    - + travis
     Status - - Coverage Status + + travis
+    Status + + + Coverage Status npm downloads @@ -88,6 +92,69 @@ export class MyComponent implements OnInit { - Translation Service - Context Menu 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. + +```javascript +import { Component } from '@angular/core'; +import { bootstrap } from '@angular/platform-browser-dynamic'; +import { HTTP_PROVIDERS } from '@angular/http'; + +import { + ALFRESCO_CORE_PROVIDERS, + AlfrescoSettingsService, + AlfrescoAuthenticationService +} from 'ng2-alfresco-core'; + +@Component({ + selector: 'my-app', + template: ` +

    +
    + Authentication successfull to ip {{ ecmHost }} with user: admin, admin, your token is {{ token }} +
    ` +}) +class MyDemoApp { + authenticated: boolean = false; + + ecmHost: string = 'http://127.0.0.1:8080'; + + token: string; + + constructor(public alfrescoAuthenticationService: AlfrescoAuthenticationService, + private alfrescoSettingsService: AlfrescoSettingsService) { + + alfrescoSettingsService.ecmHost = this.ecmHost; + alfrescoSettingsService.setProviders('ECM'); + } + + ngOnInit() { + this.login(); + } + + login() { + this.alfrescoAuthenticationService.login('admin', 'admin').subscribe( + token => { + this.token = token.ticket; + this.authenticated = true; + }, + error => { + console.log(error); + this.authenticated = false; + }); + } +} +bootstrap(MyDemoApp, [ + HTTP_PROVIDERS, + ALFRESCO_CORE_PROVIDERS +]); + +``` + + ## Build from sources Alternatively you can build component from sources with the following commands: diff --git a/ng2-components/ng2-alfresco-core/index.ts b/ng2-components/ng2-alfresco-core/index.ts index 4049aa5ebc..231df2fe5b 100644 --- a/ng2-components/ng2-alfresco-core/index.ts +++ b/ng2-components/ng2-alfresco-core/index.ts @@ -15,27 +15,20 @@ * limitations under the License. */ -import { AlfrescoSettingsService } from './src/services/AlfrescoSettingsService.service'; -import { AlfrescoTranslationLoader } from './src/services/AlfrescoTranslationLoader.service'; -import { AlfrescoTranslationService } from './src/services/AlfrescoTranslationService.service'; -import { AlfrescoPipeTranslate } from './src/services/AlfrescoPipeTranslate.service'; -import { AlfrescoAuthenticationService } from './src/services/AlfrescoAuthenticationService.service'; -import { AlfrescoContentService } from './src/services/AlfrescoContentService.service'; -import { ContextMenuService } from './src/services/context-menu.service'; -import { ContextMenuHolderComponent } from './src/components/context-menu-holder.component'; -import { ContextMenuDirective } from './src/components/context-menu.directive'; +import { + AlfrescoSettingsService, + AlfrescoTranslationLoader, + AlfrescoTranslationService, + AlfrescoPipeTranslate, + AlfrescoAuthenticationService, + AlfrescoContentService +} from './src/services/index'; -export * from './src/services/AlfrescoSettingsService.service'; -export * from './src/services/AlfrescoTranslationLoader.service'; -export * from './src/services/AlfrescoTranslationService.service'; -export * from './src/services/AlfrescoPipeTranslate.service'; -export * from './src/material/MaterialDesignLiteUpgradeElement'; -export * from './src/services/AlfrescoAuthenticationService.service'; -export * from './src/services/AlfrescoContentService.service'; +import { ContextMenuService } from './src/components/context-menu/context-menu.service'; -export * from './src/services/context-menu.service'; -export * from './src/components/context-menu-holder.component'; -export * from './src/components/context-menu.directive'; +export * from './src/services/index'; +export * from './src/components/index'; +export * from './src/utils/index'; export const ALFRESCO_CORE_PROVIDERS: [any] = [ AlfrescoAuthenticationService, @@ -47,12 +40,3 @@ export const ALFRESCO_CORE_PROVIDERS: [any] = [ ContextMenuService ]; -export const CONTEXT_MENU_PROVIDERS: [any] = [ - ContextMenuService -]; - -export const CONTEXT_MENU_DIRECTIVES: [any] = [ - ContextMenuHolderComponent, - ContextMenuDirective -]; - diff --git a/ng2-components/ng2-alfresco-core/karma-test-shim.js b/ng2-components/ng2-alfresco-core/karma-test-shim.js index d58df28667..3a95ee82c7 100644 --- a/ng2-components/ng2-alfresco-core/karma-test-shim.js +++ b/ng2-components/ng2-alfresco-core/karma-test-shim.js @@ -14,7 +14,8 @@ var map = { var packages = { 'app': { main: 'main.js', defaultExtension: 'js' }, - 'rxjs': { defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'alfresco-js-api' : { main: 'alfresco-js-api.js', defaultExtension: 'js' }, 'ng2-translate': { defaultExtension: 'js' } }; diff --git a/ng2-components/ng2-alfresco-core/karma.conf.js b/ng2-components/ng2-alfresco-core/karma.conf.js index f03e383ede..92702df655 100644 --- a/ng2-components/ng2-alfresco-core/karma.conf.js +++ b/ng2-components/ng2-alfresco-core/karma.conf.js @@ -4,17 +4,20 @@ module.exports = function (config) { var configuration = { basePath: '.', - frameworks: ['jasmine'], + frameworks: ['jasmine-ajax', 'jasmine'], files: [ // paths loaded by Karma {pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: true}, {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: false}, {pattern: 'node_modules/zone.js/dist/zone.js', included: true, watched: true}, + {pattern: 'node_modules/zone.js/dist/async-test.js', included: true, watched: true}, + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, {pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false}, {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, {pattern: 'node_modules/@angular/**/*.map', included: false, watched: false}, + {pattern: 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', included: true, watched: false}, {pattern: 'karma-test-shim.js', included: true, watched: true}, @@ -61,6 +64,7 @@ module.exports = function (config) { plugins: [ 'karma-jasmine', 'karma-coverage', + 'karma-jasmine-ajax', 'karma-chrome-launcher', 'karma-mocha-reporter', 'karma-jasmine-html-reporter' diff --git a/ng2-components/ng2-alfresco-core/package.json b/ng2-components/ng2-alfresco-core/package.json index 38aba7eff9..e884ab47ca 100644 --- a/ng2-components/ng2-alfresco-core/package.json +++ b/ng2-components/ng2-alfresco-core/package.json @@ -1,28 +1,25 @@ { "name": "ng2-alfresco-core", "description": "Alfresco Angular 2 Components core", - "version": "0.2.0", + "version": "0.3.0", "author": "Alfresco Software, Ltd.", "scripts": { + "postinstall": "typings install", "clean": "rimraf dist node_modules typings", "typings": "typings install", - "build": "npm run tslint && typings install && rimraf dist && tsc && npm run copytemplates && license-check", - "tslint": "npm run tslint-src && npm run tslint-root", - "tslint-src": "tslint -c tslint.json src/{,**/}**.ts", - "tslint-root": "tslint -c tslint.json *.ts", - "copytemplates": "npm run copy-html-css && npm run copy-i18n", - "copy-html-css": "cpx \"./src/**/*.{html,css}\" ./dist/src", - "copy-html-css:w": "cpx \"./src/**/*.{html,css}\" ./dist/src -w", - "copy-i18n": "cpx \"./i18n/**/*.json\" ./dist/i18n", - "copy-i18n:w": "cpx \"./i18n/**/*.json\" ./dist/i18n -w", - "copy-images": "cpx \"./src/**/*.{png,jpg,gif,svg}\" ./dist/src", - "copy-images:w": "cpx \"./src/**/*.{png,jpg,gif,svg}\" ./dist/src -w", + "build": "npm run tslint && rimraf dist && tsc && npm run copy-dist && license-check", + "build:w": "npm run tslint && rimraf dist && tsc && npm run copy-dist:w && npm run watch-task", + "watch-task": "concurrently \"npm run tsc:w\" \"license-check\"", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'src/{,**/}**.ts'", "licensecheck": "license-check", + "copy-dist": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src", + "copy-dist:w": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src -w", "tsc": "tsc", + "tsc:w": "tsc -w", "pretest": "npm run build", "test": "karma start karma.conf.js --reporters mocha,coverage --single-run", - "test-browser": "karma start karma.conf.js --reporters kjhtml ", - "posttest": "node_modules/.bin/remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html", + "test-browser": "npm run build && karma start karma.conf.js --reporters kjhtml ", + "posttest": "remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html && remap-istanbul -i coverage/report/coverage-final.json -o coverage/report/coverage-final.json", "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report", "prepublish": "npm run build", "travis": "echo 'placeholder'" @@ -45,6 +42,10 @@ { "name": "Mario Romano", "email": "mario.romano83@gmail.com" + }, + { + "name": "Eugenio Romano", + "email": "eugenio.romano@alfresco.com" } ], "keywords": [ @@ -54,6 +55,7 @@ "alfresco" ], "dependencies": { + "alfresco-js-api": "^0.3.0", "@angular/common": "2.0.0-rc.3", "@angular/compiler": "2.0.0-rc.3", "@angular/core": "2.0.0-rc.3", @@ -69,8 +71,7 @@ "reflect-metadata": "0.1.3", "rxjs": "5.0.0-beta.6", "zone.js": "0.6.12", - "ng2-translate": "2.2.2", - "rimraf": "2.5.2" + "ng2-translate": "2.2.2" }, "peerDependencies": { "material-design-icons": "^2.2.3", @@ -86,10 +87,12 @@ "karma-coverage": "1.0.0", "karma-coveralls": "1.1.2", "karma-jasmine": "1.0.2", + "karma-jasmine-ajax": "^0.1.13", "karma-jasmine-html-reporter": "0.2.0", "karma-mocha-reporter": "2.0.3", "license-check": "1.1.5", "remap-istanbul": "0.6.3", + "rimraf": "2.5.2", "traceur": "0.0.91", "tslint": "3.8.1", "typescript": "1.8.10", @@ -98,13 +101,7 @@ }, "license-check-config": { "src": [ - "**/*.js", - "**/*.ts", - "!/**/coverage/**/*", - "!/**/demo/**/*", - "!/**/node_modules/**/*", - "!/**/typings/**/*", - "!*.js" + "./dist/**/*.js" ], "path": "assets/license_header.txt", "blocking": true, diff --git a/ng2-components/ng2-alfresco-search/src/assets/alfresco.service.mock.ts b/ng2-components/ng2-alfresco-core/src/assets/AlfrescoApi.mock.ts similarity index 50% rename from ng2-components/ng2-alfresco-search/src/assets/alfresco.service.mock.ts rename to ng2-components/ng2-alfresco-core/src/assets/AlfrescoApi.mock.ts index 68be078741..3835e67611 100644 --- a/ng2-components/ng2-alfresco-search/src/assets/alfresco.service.mock.ts +++ b/ng2-components/ng2-alfresco-core/src/assets/AlfrescoApi.mock.ts @@ -15,29 +15,21 @@ * limitations under the License. */ -import {Observable} from 'rxjs/Rx'; -import { - AlfrescoSettingsService, - AlfrescoAuthenticationService -} from 'ng2-alfresco-core'; -import {AlfrescoSearchService} from './../../src/services/alfresco-search.service'; +export class AlfrescoApiMock { -export class AlfrescoServiceMock extends AlfrescoSearchService { - - _folderToReturn: any = {}; - - constructor( - settings: AlfrescoSettingsService = null, - authService: AlfrescoAuthenticationService = null - ) { - super(settings, authService); - } - - getFolder(folder: string) { - return Observable.create(observer => { - observer.next(this._folderToReturn); - observer.complete(); + login(username: string, password: string) { + return new Promise((resolve) => { + resolve('TICKET_4479f4d3bb155195879bfbb8d5206f433488a1b1'); }); } + + logout() { + return new Promise((resolve) => { + resolve('logout'); + }); + } + + changeConfig() { + } } diff --git a/ng2-components/ng2-alfresco-core/src/components/context-menu-holder.component.spec.ts b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.spec.ts similarity index 97% rename from ng2-components/ng2-alfresco-core/src/components/context-menu-holder.component.spec.ts rename to ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.spec.ts index 496fac21e7..5ac723cc3d 100644 --- a/ng2-components/ng2-alfresco-core/src/components/context-menu-holder.component.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.spec.ts @@ -16,7 +16,7 @@ */ import { describe, it, beforeEach } from '@angular/core/testing'; -import { ContextMenuService } from './../services/context-menu.service'; +import { ContextMenuService } from './context-menu.service'; import { ContextMenuHolderComponent } from './context-menu-holder.component'; describe('ContextMenuHolderComponent', () => { diff --git a/ng2-components/ng2-alfresco-core/src/components/context-menu-holder.component.ts b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.ts similarity index 89% rename from ng2-components/ng2-alfresco-core/src/components/context-menu-holder.component.ts rename to ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.ts index 03eeb3925f..9f406ed117 100644 --- a/ng2-components/ng2-alfresco-core/src/components/context-menu-holder.component.ts +++ b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { Component } from '@angular/core'; -import { ContextMenuService } from './../services/context-menu.service'; +import { Component, HostListener } from '@angular/core'; +import { ContextMenuService } from './context-menu.service'; @Component({ selector:'context-menu-holder', @@ -52,10 +52,6 @@ import { ContextMenuService } from './../services/context-menu.service'; } ` ], - host:{ - '(document:click)':'clickedOutside()' - // '(click)':'clickInside()' - }, template: `