diff --git a/.editorconfig b/.editorconfig index 8ed330c4a2..c310eb95da 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,10 +1,19 @@ +# http://editorconfig.org 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 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[package.json] +indent_style = space +indent_size = 2 + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..5f4e5c9b4f --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,32 @@ + + +**Type of issue:** (check with "[x]") +- [ ] New feature request +- [ ] Bug +- [ ] Support request + +**Current behavior:** + + +**Expected behavior:** + + +**Steps to reproduce the issue:** + + +**Component name and version:** + + +**Browser and version:** + + +**Node version (for build issues):** + + +**New feature request:** + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..f0845685b0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ + + +**Type of contribution:** (check one with "x") +``` +[ ] Bugfix +[ ] Feature +[ ] Code formatting +[ ] Code Refactoring +[ ] Build related changes +[ ] Documentation +[ ] Other... Please describe: +``` +**This PR adds the following feature:** + + +**Current behavior:** + +**New behavior:** + +**This PR fixes the following issue:** + + +**This PR introduces a breaking change:** (check one with "x") +- [ ] Yes +- [ ] No + + + + +**More information:** + diff --git a/.gitignore b/.gitignore index fd050d0b70..62258c2884 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,8 @@ node_modules workspace.xml .idea/ dist/ -!systemjs.config.js \ No newline at end of file +!systemjs.config.js +demo-shell-ng2/app/components/router/ +ng2-components/ng2-alfresco-userinfo-old/demo/src/app/ +ng2-components/ng2-alfresco-userinfo-old/src/services/bpm-user.service.spec.ts +ng2-components/ng2-alfresco-userinfo-old/src/services/ecm-user.service.spec.ts diff --git a/.travis.yml b/.travis.yml index 871656636c..a3786026b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,9 +27,13 @@ env: - MODULE=ng2-alfresco-upload - MODULE=ng2-alfresco-viewer - MODULE=ng2-alfresco-webscript + - MODULE=ng2-alfresco-tag - MODULE=ng2-activiti-form - MODULE=ng2-activiti-tasklist - MODULE=ng2-activiti-processlist + - MODULE=ng2-activiti-diagrams + - MODULE=ng2-activiti-analytics + - MODULE=ng2-alfresco-userinfo before_script: - if ([ "$MODULE" != "ng2-alfresco-core" ]); then @@ -44,21 +48,12 @@ before_script: - 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 + - if ([ "$MODULE" == "ng2-activiti-analytics" ]); then + (cd ng2-components/ng2-activiti-diagrams; npm link ng2-alfresco-core; 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 + - npm install; - ls -ltrh ./node_modules/ script: npm run test # Send coverage data to Coveralls @@ -79,3 +74,7 @@ cache: - ng2-components/ng2-alfresco-upload/node_modules - ng2-components/ng2-alfresco-viewer/node_modules - ng2-components/ng2-alfresco-webscript/node_modules + - ng2-components/ng2-alfresco-tag/node_modules + - ng2-components/ng2-activiti-analytics/node_modules + - ng2-components/ng2-alfresco-userinfo/node_modules + - ng2-components/ng2-activiti-diagrams/node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..ee92513479 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/.DS_Store": true, + "**/*.js": { "when": "$(basename).ts"}, + "**/*.js.map": { "when": "$(basename)"} + } +} diff --git a/BROWSER-SUPPORT.md b/BROWSER-SUPPORT.md new file mode 100644 index 0000000000..1b5bd199a3 --- /dev/null +++ b/BROWSER-SUPPORT.md @@ -0,0 +1,55 @@ +# Browser Support +Browser support and polyfills guide. + +Browser compatibility and support depends on targeted browsers and 3rd party libraries. ADF framework is based on the following libraries and components: + +- Angular 2 (all ADF components) +- Material Design Lite (all ADF components) +- Moment.js (many ADF components) +- PDF.js (`ng2-alfresco-viewer` component) +- Raphael.js (`ng2-alfresco-diagrams`, `ng2-alfresco-analytics`) +- Chart.js (`ng2-alfresco-analytics`) +- Material Design - Date and Time Picker (`ng2-activiti-form`, `ng2-alfresco-analytics`) + +## Browser polyfills + +### Angular 2 +Please refer to the [official guide](https://angular.io/docs/ts/latest/guide/browser-support.html) for Angular 2 browser support. + +ADF (demo shell) imports by default the following set of recommended polyfills: + +- [core-js](https://www.npmjs.com/package/core-js) (ES6 standard support) + +### 3rd party libraries +Please refer to the following list of [popular polyfills](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills) for HTML5 cross-browser compatibility. + +ADF (demo shell) imports by default the following set of recommended polyfills: + +- [intl](https://www.npmjs.com/package/intl), Polyfill the ECMA-402 Intl API (except collation), **IE**/**Safari** +- [dom4](https://github.com/WebReflection/dom4), A fully tested and covered polyfill for new DOM Level 4 entries, **IE** +- [element.scrollintoviewifneeded-polyfill](https://www.npmjs.com/package/element.scrollintoviewifneeded-polyfill), simple JavaScript implementation of the non-standard WebKit method scrollIntoViewIfNeeded that can be called on DOM elements, **IE**/**Firefox**/**Safari**, required only to support **Material Design - Date and Time Picker** component +- [pdfjs compatibility](https://www.npmjs.com/package/pdfjs-dist), Portable Document Format (PDF) library that is built with HTML5, **IE** +- (dialog-polyfill)[https://www.npmjs.com/package/dialog-polyfill], Polyfill for the dialog element, **IE**/**Safari**/**Firefox** + +## Example + +```html + + + + + + + + + + + + + + + + +``` + +For a complete code please refer to [demo shell](demo-shell-ng2/index.html) example. diff --git a/PREREQUISITES.md b/PREREQUISITES.md index 7687ee6ec4..0bb6211056 100644 --- a/PREREQUISITES.md +++ b/PREREQUISITES.md @@ -2,94 +2,18 @@ The [Angular 2](https://angular.io/) based application development framework requires the following: -- 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. +- An Alfresco Platform Repository (version [201609 Early Access](https://community.alfresco.com/docs/DOC-6372-alfresco-community-edition-file-list-201609-ea) or newer) to talk to, which has [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) enabled. +- [Download and install Activiti](https://www.alfresco.com/products/bpm/alfresco-activiti/trial) - [Node.js](https://nodejs.org/en/) JavaScript runtime. - [npm](https://www.npmjs.com/) package manager for JavaScript. - +- (If you use ECM and BPM together) Make sure your user has the same username and password in both system + +*Note: Default username for activiti is "admin@app.activiti.com" and "admin" for Alfresco, and also the default password are different. Change them to be equal.* + **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 - -Alfresco comes with installers that will install all the servers, webapps, and tools needed to run Alfresco. - -- Download Alfresco Community from this [page](https://www.alfresco.com/products/community/download). -- Install Alfresco following these [instructions](http://docs.alfresco.com/5.1/concepts/installs-eval-intro.html). - -This will install the following Alfresco web applications: - -- Alfresco Platform with the Content Repository, which we need so we can access content from our custom web client -- Alfresco Solr, which we need so we can search for content from our custom web client -- Alfresco Share, not technically needed, but can be useful for creating users and groups, and to access and upload content to the repository while we are developing the custom web client - -### Enable CORS in Alfresco - -The web client that we are building with the application development framework will be loaded from a different web server than the Alfresco Platform is running on. -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 one of the following: - -**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`: - -``` - - CORS - com.thetransactioncompany.cors.CORSFilter - - cors.allowGenericHttpRequests - true - - - cors.allowOrigin - http://localhost:3000 - - - cors.allowSubdomains - true - - - cors.supportedMethods - GET, HEAD, POST, PUT, DELETE, OPTIONS - - - cors.supportedHeaders - origin, authorization, x-file-size, x-file-name, content-type, accept, x-file-type - - - cors.supportsCredentials - true - - - cors.maxAge - 3600 - - -``` -When specifying the `cors.allowOrigin` URL make sure to use the URL that will be used by the web client. - -Then uncomment filter mappings: - -``` - - CORS - /api/* - /service/* - /s/* - /cmisbrowser/* - -``` ## Installing Node.js If you don't have Node.js installed then access this [page](https://nodejs.org/en/download/) and use the appropriate installer for your OS. @@ -101,4 +25,26 @@ $ node -v v5.12.0 ``` +## Configure Nginx +To correctly configure Nginx use the following file [nginx.conf](/nginx.conf). +This will put Activiti, Alfresco and the app dev framework under the same domain. + +* ECM : http://localhost:8888/alfresco/ +* BPM : http://localhost:8888/activiti/ + +To make everything work, you have to change the address of the ECM and BPM. In the demo app you can do that clicking on the top left menu and changing the bottom left options: ECM host and BPM host. + +This configuration assumes few things: + +* Port mapping: + * Nginx entry point: 8888 + * Demo Shell: 3000 + * Alfresco: 8080 + * Activiti: 9999 + +All those values can be modified at their respective `location` directive on the [nginx.conf](/nginx.conf) file. + +It also need to be compiled with the [Headers More](https://www.nginx.com/resources/wiki/modules/headers_more/) module , which add more control over sending headers to the backend. + +If you want to know more on how to install and configure Nginx to work with the Application Development Framework can be found [here](https://community.alfresco.com/community/application-development-framework/blog/2016/09/28/adf-development-set-up-with-nginx-proxy) diff --git a/README.md b/README.md index f0f4e7eb0c..f993fe52d5 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@

alfresco - angular2 + angular2

@@ -52,9 +52,9 @@ necessary configuration, see this [page](PREREQUISITES.md). ## Components To view the complete list of all the components that you can use to build your custom Alfresco (ECM,BPM) client follow this link: -[Components](https://github.com/Alfresco/alfresco-ng2-components/tree/master/ng2-components). +[Components](/ng2-components). -You can browse all the components also at the following [page](http://devproducts.alfresco.com/). +You can browse all the components at the following [page](http://devproducts.alfresco.com/). ## Yeoman generators @@ -89,3 +89,17 @@ To deploy directly on your AWS instance our demo shell click the button below: +## Browser Support +All components are supported in the below browsers: + +|**Browser** |**Version** | +|--- |--- | +|Chrome |Latest | +|Safari (OS X) |9.x | +|Firefox* |Latest | +|Edge |13, 14 | +|Internet Explorer |11 | + +*Concerning Alfresco Upload Component, folder upload currently not supported [firefox known issue](https://bugzilla.mozilla.org/show_bug.cgi?id=1188880 ) + +Please refer to [Browser Support](BROWSER-SUPPORT.md) article for more details. diff --git a/appveyor.yml b/appveyor.yml index 6487d8bafb..7e892d3323 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,10 +14,14 @@ environment: - COMPONENT_NAME: ng2-alfresco-search - COMPONENT_NAME: ng2-alfresco-upload - COMPONENT_NAME: ng2-alfresco-viewer + - COMPONENT_NAME: ng2-alfresco-tag - COMPONENT_NAME: ng2-alfresco-webscript - COMPONENT_NAME: ng2-activiti-form - COMPONENT_NAME: ng2-activiti-tasklist - COMPONENT_NAME: ng2-activiti-processlist + - COMPONENT_NAME: ng2-activiti-diagrams + - COMPONENT_NAME: ng2-activiti-analytics + - COMPONENT_NAME: ng2-alfresco-userinfo # Install scripts. (runs after repo cloning) install: @@ -35,10 +39,10 @@ install: - 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-processlist (npm link ng2-alfresco-datatable && npm link ng2-activiti-form && 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) + - IF %COMPONENT_NAME% EQU ng2-activiti-analytics (npm link ng2-alfresco-diagrams) - npm install # Post-install test scripts. diff --git a/demo-shell-ng2/.gitignore b/demo-shell-ng2/.gitignore index 964913e5ef..022350903b 100644 --- a/demo-shell-ng2/.gitignore +++ b/demo-shell-ng2/.gitignore @@ -3,6 +3,7 @@ node_modules/ bower_components/ app/**/*.js app/**/*.js.map +!app/js/Polyline.js .idea dist/ diff --git a/demo-shell-ng2/app/app.component.css b/demo-shell-ng2/app/app.component.css index 87031d33ee..6eff75fc29 100644 --- a/demo-shell-ng2/app/app.component.css +++ b/demo-shell-ng2/app/app.component.css @@ -3,3 +3,15 @@ display: none; } } + +.mdl-layout-title { + font-size: 17px; +} + +.user-profile { + margin-right: 50px; +} + +.mdl-button-padding { + padding-left: 20px; +} diff --git a/demo-shell-ng2/app/app.component.html b/demo-shell-ng2/app/app.component.html index c5d2a1d1e7..bbea07d6f6 100644 --- a/demo-shell-ng2/app/app.component.html +++ b/demo-shell-ng2/app/app.component.html @@ -1,68 +1,83 @@

-
-
- - Demo Application - -
+
+
+
- - +
+ +
- -
+
+
+ Components List + - - - - -
    -
  • English
  • -
  • Greek
  • -
  • Italian
  • -
  • More
  • - Login -
  • Logout
  • -
-
-
- 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 415c37fcc2..2b1e360ca4 100644 --- a/demo-shell-ng2/app/app.component.ts +++ b/demo-shell-ng2/app/app.component.ts @@ -16,29 +16,22 @@ */ import { Component } from '@angular/core'; -import { ROUTER_DIRECTIVES, Router } from '@angular/router'; +import { Router } from '@angular/router'; import { - MDL, - AlfrescoSettingsService, AlfrescoTranslationService, - AlfrescoPipeTranslate, - AlfrescoAuthenticationService + AlfrescoAuthenticationService, + AlfrescoSettingsService } from 'ng2-alfresco-core'; -import { SearchBarComponent } from './components/index'; - declare var document: any; @Component({ selector: 'alfresco-app', templateUrl: 'app/app.component.html', - styleUrls: ['app/app.component.css'], - directives: [SearchBarComponent, ROUTER_DIRECTIVES, MDL], - pipes: [AlfrescoPipeTranslate] + styleUrls: ['app/app.component.css'] }) export class AppComponent { - translate: AlfrescoTranslationService; searchTerm: string = ''; ecmHost: string = 'http://' + window.location.hostname + ':8080'; @@ -46,38 +39,46 @@ export class AppComponent { constructor(public auth: AlfrescoAuthenticationService, public router: Router, - translate: AlfrescoTranslationService, - public alfrescoSettingsService: AlfrescoSettingsService) { + public alfrescoSettingsService: AlfrescoSettingsService, + private translate: AlfrescoTranslationService) { this.setEcmHost(); this.setBpmHost(); + this.setProvider(); - this.translate = translate; - this.translate.addTranslationFolder(); - } - - 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); + if (translate) { + translate.addTranslationFolder(); + } } isLoggedIn(): boolean { + this.redirectToLoginPageIfNotLoggedIn(); return this.auth.isLoggedIn(); } + redirectToLoginPageIfNotLoggedIn(): void { + if (!this.isLoginPage() && !this.auth.isLoggedIn()) { + this.router.navigate(['/login']); + } + } + + isLoginPage(): boolean { + return location.pathname === '/login' || location.pathname === '/' || location.pathname === '/settings'; + } + onLogout(event) { event.preventDefault(); this.auth.logout() .subscribe( - () => this.router.navigate(['/login']) + () => { + this.router.navigate(['/login']); + }, + ($event: any) => { + if ($event && $event.response && $event.response.status === 401) { + this.router.navigate(['/login']); + } else { + console.error('An unknown error occurred while logging out', $event); + } + } ); } @@ -117,4 +118,10 @@ export class AppComponent { this.alfrescoSettingsService.bpmHost = this.bpmHost; } } + + private setProvider() { + if (localStorage.getItem(`providers`)) { + this.alfrescoSettingsService.setProviders(localStorage.getItem(`providers`)); + } + } } diff --git a/demo-shell-ng2/app/app.module.ts b/demo-shell-ng2/app/app.module.ts new file mode 100644 index 0000000000..37e34f4dcb --- /dev/null +++ b/demo-shell-ng2/app/app.module.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. + */ + +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { CoreModule } from 'ng2-alfresco-core'; +import { SearchModule } from 'ng2-alfresco-search'; +import { LoginModule } from 'ng2-alfresco-login'; +import { DataTableModule } from 'ng2-alfresco-datatable'; +import { DocumentListModule } from 'ng2-alfresco-documentlist'; +import { UploadModule } from 'ng2-alfresco-upload'; +import { TagModule } from 'ng2-alfresco-tag'; +import { WebScriptModule } from 'ng2-alfresco-webscript'; +import { ViewerModule } from 'ng2-alfresco-viewer'; +import { ActivitiFormModule } from 'ng2-activiti-form'; +import { ActivitiTaskListModule } from 'ng2-activiti-tasklist'; +import { ActivitiProcessListModule } from 'ng2-activiti-processlist'; +import { UserInfoComponentModule } from 'ng2-alfresco-userinfo'; +import { AnalyticsModule } from 'ng2-activiti-analytics'; + +import { AppComponent } from './app.component'; +import { routing } from './app.routes'; + +import { + DataTableDemoComponent, + SearchComponent, + SearchBarComponent, + LoginDemoComponent, + ActivitiDemoComponent, + FormViewer, + WebscriptComponent, + TagComponent, + AboutComponent, + FilesComponent, + FormNodeViewer, + SettingComponent +} from './components/index'; + +@NgModule({ + imports: [ + BrowserModule, + routing, + CoreModule.forRoot(), + LoginModule, + SearchModule.forRoot(), + DataTableModule, + DocumentListModule.forRoot(), + UploadModule.forRoot(), + TagModule.forRoot(), + WebScriptModule, + ViewerModule.forRoot(), + ActivitiFormModule.forRoot(), + ActivitiTaskListModule.forRoot(), + ActivitiProcessListModule.forRoot(), + UserInfoComponentModule.forRoot(), + AnalyticsModule.forRoot() + ], + declarations: [ + AppComponent, + SearchBarComponent, + DataTableDemoComponent, + SearchComponent, + SearchBarComponent, + LoginDemoComponent, + ActivitiDemoComponent, + FormViewer, + WebscriptComponent, + TagComponent, + AboutComponent, + FilesComponent, + FormNodeViewer, + SettingComponent + ], + providers: [], + bootstrap: [ AppComponent ] +}) +export class AppModule { } + diff --git a/demo-shell-ng2/app/app.routes.ts b/demo-shell-ng2/app/app.routes.ts index feaa94f7c0..234bfcdc6f 100644 --- a/demo-shell-ng2/app/app.routes.ts +++ b/demo-shell-ng2/app/app.routes.ts @@ -15,22 +15,26 @@ * limitations under the License. */ -import { provideRouter, RouterConfig } from '@angular/router'; +import { ModuleWithProviders } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; import { FilesComponent, - UploadButtonComponent, DataTableDemoComponent, SearchComponent, LoginDemoComponent, ActivitiDemoComponent, WebscriptComponent, + TagComponent, AboutComponent, - FormViewer + FormViewer, + FormNodeViewer, + SettingComponent } from './components/index'; -import { FormNodeViewer } from './components/activiti/form-node-viewer.component'; -export const routes: RouterConfig = [ +import { UploadButtonComponent } from 'ng2-alfresco-upload'; + +export const appRoutes: Routes = [ { path: 'home', component: FilesComponent }, { path: 'files', component: FilesComponent }, { path: 'datatable', component: DataTableDemoComponent }, @@ -39,12 +43,13 @@ export const routes: RouterConfig = [ { path: 'login', component: LoginDemoComponent }, { path: 'search', component: SearchComponent }, { path: 'activiti', component: ActivitiDemoComponent }, + { path: 'activiti/appId/:appId', component: ActivitiDemoComponent }, { path: 'activiti/tasks/:id', component: FormViewer }, { path: 'activiti/tasksnode/:id', component: FormNodeViewer }, { path: 'webscript', component: WebscriptComponent }, - { path: 'about', component: AboutComponent } + { path: 'tag', component: TagComponent }, + { path: 'about', component: AboutComponent }, + { path: 'settings', component: SettingComponent } ]; -export const appRouterProviders = [ - provideRouter(routes) -]; +export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes); diff --git a/demo-shell-ng2/app/components/about/about.component.ts b/demo-shell-ng2/app/components/about/about.component.ts index 30b667a14a..93320f2746 100644 --- a/demo-shell-ng2/app/components/about/about.component.ts +++ b/demo-shell-ng2/app/components/about/about.component.ts @@ -17,21 +17,14 @@ import { Component, OnInit } from '@angular/core'; import { Http } from '@angular/http'; -import { - ALFRESCO_DATATABLE_DIRECTIVES, - ObjectDataTableAdapter /*, - DataSorting, - ObjectDataRow, - ObjectDataColumn*/ -} from 'ng2-alfresco-datatable'; +import { ObjectDataTableAdapter } from 'ng2-alfresco-datatable'; declare let __moduleName: string; @Component({ moduleId: __moduleName, selector: 'about-page', - templateUrl: './about.component.html', - directives: [ALFRESCO_DATATABLE_DIRECTIVES] + templateUrl: './about.component.html' }) export class AboutComponent implements OnInit { diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.html b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html index 988b262764..11f6544900 100644 --- a/demo-shell-ng2/app/components/activiti/activiti-demo.component.html +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html @@ -1,65 +1,98 @@
+
- -
-
-
+ +
+ + + +
+
+ +
+
+ + + + +
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 index b30933a0c0..37bc4c51f2 100644 --- a/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts @@ -16,9 +16,19 @@ */ 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'; +import { + AppDefinitionRepresentationModel, + FilterRepresentationModel, + ActivitiApps, + ActivitiTaskList +} from 'ng2-activiti-tasklist'; +import { ActivitiProcessInstanceListComponent } from 'ng2-activiti-processlist'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs/Rx'; +import { + ObjectDataTableAdapter, + DataSorting +} from 'ng2-alfresco-datatable'; declare let __moduleName: string; declare var componentHandler; @@ -27,25 +37,41 @@ declare var componentHandler; moduleId: __moduleName, selector: 'activiti-demo', templateUrl: './activiti-demo.component.html', - styleUrls: ['./activiti-demo.component.css'], - directives: [ALFRESCO_TASKLIST_DIRECTIVES, ACTIVITI_PROCESSLIST_DIRECTIVES, ActivitiForm] + styleUrls: ['./activiti-demo.component.css'] }) export class ActivitiDemoComponent implements AfterViewChecked { - currentChoice: string = 'task-list'; + @ViewChild('activitiapps') + activitiapps: ActivitiApps; + + @ViewChild('activitifilter') + activitifilter: any; @ViewChild('activitidetails') activitidetails: any; - @ViewChild('activititasklist') - activititasklist: any; + @ViewChild(ActivitiTaskList) + activititasklist: ActivitiTaskList; - @ViewChild('activitiprocesslist') - activitiprocesslist: any; + @ViewChild('activitiprocessfilter') + activitiprocessfilter: any; + + @ViewChild(ActivitiProcessInstanceListComponent) + activitiprocesslist: ActivitiProcessInstanceListComponent; @ViewChild('activitiprocessdetails') activitiprocessdetails: any; + @ViewChild('tabmain') + tabMain: any; + + @ViewChild('tabheader') + tabHeader: any; + + @Input() + appId: number; + + layoutType: string; currentTaskId: string; currentProcessInstanceId: string; @@ -53,51 +79,106 @@ export class ActivitiDemoComponent implements AfterViewChecked { processSchemaColumns: any [] = []; taskFilter: any; + report: any; processFilter: any; - @Input() - appId: string; + sub: Subscription; - setChoice($event) { - this.currentChoice = $event.target.value; + dataTasks: ObjectDataTableAdapter; + dataProcesses: ObjectDataTableAdapter; + + constructor(private route: ActivatedRoute) { + this.dataTasks = new ObjectDataTableAdapter( + [], + [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'created', title: 'Created', cssClass: 'hidden', sortable: true} + ] + ); + this.dataTasks.setSorting(new DataSorting('created', 'desc')); + + this.dataProcesses = new ObjectDataTableAdapter( + [], + [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'started', title: 'Started', cssClass: 'hidden', sortable: true} + ] + ); + this.dataProcesses.setSorting(new DataSorting('started', 'desc')); } - isProcessListSelected() { - return this.currentChoice === 'process-list'; + ngOnInit() { + this.sub = this.route.params.subscribe(params => { + this.appId = params['appId']; + }); + this.layoutType = ActivitiApps.LAYOUT_GRID; } - isTaskListSelected() { - return this.currentChoice === 'task-list'; + ngOnDestroy() { + this.sub.unsubscribe(); } - 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} - ]; + onAppClick(app: AppDefinitionRepresentationModel) { + this.appId = app.id; + this.taskFilter = null; + this.currentTaskId = null; + + this.processFilter = null; + this.currentProcessInstanceId = null; + + this.changeTab('apps', 'tasks'); } - onTaskFilterClick(event: any) { + changeTab(origin: string, destination: string) { + this.tabMain.nativeElement.children[origin].classList.remove('is-active'); + this.tabMain.nativeElement.children[destination].classList.add('is-active'); + + this.tabHeader.nativeElement.children[`${origin}-header`].classList.remove('is-active'); + this.tabHeader.nativeElement.children[`${destination}-header`].classList.add('is-active'); + } + + onTaskFilterClick(event: FilterRepresentationModel) { this.taskFilter = event; - this.activititasklist.load(this.taskFilter); + } + + onReportClick(event: any) { + this.report = event; + } + + onSuccessTaskFilterList(event: any) { + this.taskFilter = this.activitifilter.getCurrentFilter(); + } + + onStartTaskSuccess(event: any) { + this.activititasklist.reload(); + } + + onSuccessTaskList(event: FilterRepresentationModel) { + this.currentTaskId = this.activititasklist.getCurrentTaskId(); } onProcessFilterClick(event: any) { - this.processFilter = event.filter; - this.activitiprocesslist.load(this.processFilter); + this.processFilter = event; + } + + onSuccessProcessFilterList(event: any) { + this.processFilter = this.activitiprocessfilter.getCurrentFilter(); + } + + onSuccessProcessList(event: any) { + this.currentProcessInstanceId = this.activitiprocesslist.getCurrentProcessId(); } onTaskRowClick(taskId) { this.currentTaskId = taskId; - this.activitidetails.loadDetails(this.currentTaskId); } onProcessRowClick(processInstanceId) { this.currentProcessInstanceId = processInstanceId; - this.activitiprocessdetails.load(this.currentProcessInstanceId); + } + + onStartProcessInstance() { + this.activitiprocesslist.reload(); } processCancelled(data: any) { @@ -105,10 +186,19 @@ export class ActivitiDemoComponent implements AfterViewChecked { this.activitiprocesslist.reload(); } + onSuccessNewProcess(data: any) { + this.activitiprocesslist.reload(); + } + taskFormCompleted(data: any) { this.activitiprocesslist.reload(); } + onFormCompleted(form) { + this.activititasklist.load(this.taskFilter); + this.currentTaskId = null; + } + ngAfterViewChecked() { // workaround for MDL issues with dynamic components if (componentHandler) { 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 index 1b5f994660..a73a709543 100644 --- a/demo-shell-ng2/app/components/activiti/form-node-viewer.component.ts +++ b/demo-shell-ng2/app/components/activiti/form-node-viewer.component.ts @@ -16,8 +16,7 @@ */ import { Component, OnInit, OnDestroy, AfterViewChecked } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form'; +import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs/Rx'; declare let __moduleName: string; @@ -27,9 +26,7 @@ declare var componentHandler; moduleId: __moduleName, selector: 'form-node-viewer', templateUrl: './form-node-viewer.component.html', - styleUrls: ['./form-node-viewer.component.css'], - directives: [ActivitiForm], - providers: [FormService, EcmModelService, NodeService] + styleUrls: ['./form-node-viewer.component.css'] }) export class FormNodeViewer implements OnInit, OnDestroy, AfterViewChecked { @@ -37,9 +34,7 @@ export class FormNodeViewer implements OnInit, OnDestroy, AfterViewChecked { private sub: Subscription; - constructor(private formService: FormService, - private route: ActivatedRoute, - private router: Router) { + constructor(private route: ActivatedRoute) { } ngOnInit() { diff --git a/demo-shell-ng2/app/components/activiti/form-viewer.component.ts b/demo-shell-ng2/app/components/activiti/form-viewer.component.ts index 2acfc75777..6998e1fcb5 100644 --- a/demo-shell-ng2/app/components/activiti/form-viewer.component.ts +++ b/demo-shell-ng2/app/components/activiti/form-viewer.component.ts @@ -16,8 +16,7 @@ */ import { Component, OnInit, OnDestroy, AfterViewChecked } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form'; +import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs/Rx'; declare let __moduleName: string; @@ -27,9 +26,7 @@ declare var componentHandler; moduleId: __moduleName, selector: 'form-viewer', templateUrl: './form-viewer.component.html', - styleUrls: ['./form-viewer.component.css'], - directives: [ActivitiForm], - providers: [FormService, EcmModelService, NodeService] + styleUrls: ['./form-viewer.component.css'] }) export class FormViewer implements OnInit, OnDestroy, AfterViewChecked { @@ -37,9 +34,7 @@ export class FormViewer implements OnInit, OnDestroy, AfterViewChecked { private sub: Subscription; - constructor(private formService: FormService, - private route: ActivatedRoute, - private router: Router) { + constructor(private route: ActivatedRoute) { } ngOnInit() { diff --git a/demo-shell-ng2/app/components/datatable/datatable-demo.component.html b/demo-shell-ng2/app/components/datatable/datatable-demo.component.html index 2f90bb4f18..795c40adb2 100644 --- a/demo-shell-ng2/app/components/datatable/datatable-demo.component.html +++ b/demo-shell-ng2/app/components/datatable/datatable-demo.component.html @@ -9,22 +9,22 @@
diff --git a/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts b/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts index c9c183222d..ae615fe7e6 100644 --- a/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts +++ b/demo-shell-ng2/app/components/datatable/datatable-demo.component.ts @@ -17,7 +17,6 @@ import { Component } from '@angular/core'; import { - ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter, DataSorting, ObjectDataRow, @@ -29,8 +28,7 @@ declare let __moduleName: string; @Component({ moduleId: __moduleName, selector: 'datatable-demo', - templateUrl: './datatable-demo.component.html', - directives: [ALFRESCO_DATATABLE_DIRECTIVES] + templateUrl: './datatable-demo.component.html' }) export class DataTableDemoComponent { diff --git a/demo-shell-ng2/app/components/files/files.component.html b/demo-shell-ng2/app/components/files/files.component.html index fabeb89aef..1ea0941097 100644 --- a/demo-shell-ng2/app/components/files/files.component.html +++ b/demo-shell-ng2/app/components/files/files.component.html @@ -1,195 +1,196 @@ -
- - - - +
+ - - - - - + [versioning] = "versioning" + (onSuccess)="documentList.reload()"> + + + - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
    +
  • Current path: {{documentList.currentFolderPath}}
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ + +

+ +

+ + +

+ +

+ +

+ +

+ +

+ +

+ +
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 c6d8e3fad9..817d7279b0 100644 --- a/demo-shell-ng2/app/components/files/files.component.ts +++ b/demo-shell-ng2/app/components/files/files.component.ts @@ -17,24 +17,14 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; +import { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; import { - DOCUMENT_LIST_DIRECTIVES, - DOCUMENT_LIST_PROVIDERS, DocumentActionsService, DocumentList, ContentActionHandler, DocumentActionModel, FolderActionModel } from 'ng2-alfresco-documentlist'; -import { - MDL, - AlfrescoContentService, - 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; @@ -43,17 +33,7 @@ declare let __moduleName: string; moduleId: __moduleName, selector: 'files-component', templateUrl: './files.component.html', - styleUrls: ['./files.component.css'], - directives: [ - DOCUMENT_LIST_DIRECTIVES, - MDL, - ALFRESCO_ULPOAD_COMPONENTS, - VIEWERCOMPONENT, - CONTEXT_MENU_DIRECTIVES, - PaginationComponent - ], - providers: [DOCUMENT_LIST_PROVIDERS, FormService], - pipes: [AlfrescoPipeTranslate] + styleUrls: ['./files.component.css'] }) export class FilesComponent implements OnInit { currentPath: string = '/Sites/swsdp/documentLibrary'; @@ -70,8 +50,8 @@ export class FilesComponent implements OnInit { @ViewChild(DocumentList) documentList: DocumentList; - constructor(private contentService: AlfrescoContentService, - private documentActions: DocumentActionsService, + constructor(private documentActions: DocumentActionsService, + public auth: AlfrescoAuthenticationService, private formService: FormService, private router: Router) { documentActions.setHandler('my-handler', this.myDocumentActionHandler.bind(this)); @@ -126,10 +106,14 @@ export class FilesComponent implements OnInit { } ngOnInit() { - this.formService.getProcessDefinitions().subscribe( - defs => this.setupBpmActions(defs || []), - err => console.log(err) - ); + if ( this.auth.isBpmLoggedIn() ) { + this.formService.getProcessDefinitions().subscribe( + defs => this.setupBpmActions(defs || []), + err => console.log(err) + ); + } else { + console.log('You are not logged in'); + } } viewActivitiForm(event?: any) { diff --git a/demo-shell-ng2/app/components/index.ts b/demo-shell-ng2/app/components/index.ts index 368d7ea43e..28ca7a9b56 100644 --- a/demo-shell-ng2/app/components/index.ts +++ b/demo-shell-ng2/app/components/index.ts @@ -15,7 +15,6 @@ * 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'; @@ -23,5 +22,8 @@ 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 { TagComponent } from './tag/tag.component'; export { AboutComponent } from './about/about.component'; export { FilesComponent } from './files/files.component'; +export { FormNodeViewer } from './activiti/form-node-viewer.component'; +export { SettingComponent } from './setting/setting.component'; diff --git a/demo-shell-ng2/app/components/login/login-demo.component.css b/demo-shell-ng2/app/components/login/login-demo.component.css new file mode 100644 index 0000000000..d3b186c7c4 --- /dev/null +++ b/demo-shell-ng2/app/components/login/login-demo.component.css @@ -0,0 +1,14 @@ +.setting { + border-radius: 8px; position: absolute; background-color: papayawhip; color: cadetblue; left: 10px; top: 10px; z-index: 1; +} +.banned{ + width:130px;margin: 10px; +} + +.toggle { + width:120px;margin: 20px; +} + +.setting-button { + position: absolute; right: 10px; top: 10px; z-index: 1; +} 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 090f4c3f4f..b77993525d 100644 --- a/demo-shell-ng2/app/components/login/login-demo.component.html +++ b/demo-shell-ng2/app/components/login/login-demo.component.html @@ -1,15 +1,36 @@ -
-

+

+

-

+

+

+ +

+

+
+ +

- + + + + + 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 e7533e7794..868b866608 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_DIRECTIVES, Router} from '@angular/router'; +import { Component, ViewChild, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Validators } from '@angular/forms'; declare let __moduleName: string; @@ -25,14 +25,51 @@ declare let __moduleName: string; moduleId: __moduleName, selector: 'login-demo', templateUrl: './login-demo.component.html', - directives: [ROUTER_DIRECTIVES, AlfrescoLoginComponent], - pipes: [] + styleUrls: ['./login-demo.component.css'] }) -export class LoginDemoComponent { +export class LoginDemoComponent implements OnInit { + + @ViewChild('alfrescologin') + alfrescologin: any; providers: string = 'ECM'; + disableCsrf: boolean = false; + blackListUsername: string; + customValidation: any; + + isECM: boolean = true; + isBPM: boolean = false; constructor(public router: Router) { + this.customValidation = { + username: ['', Validators.compose([Validators.required, Validators.minLength(4)])], + password: ['', Validators.required] + }; + } + + ngOnInit() { + this.alfrescologin.addCustomValidationError('username', 'required', 'LOGIN.MESSAGES.USERNAME-REQUIRED'); + this.alfrescologin.addCustomValidationError('username', 'minlength', 'LOGIN.MESSAGES.USERNAME-MIN'); + this.alfrescologin.addCustomValidationError('password', 'required', 'LOGIN.MESSAGES.PASSWORD-REQUIRED'); + + if (localStorage.getItem('providers')) { + this.providers = localStorage.getItem('providers'); + } + + this.setProviders(); + } + + setProviders() { + if (this.providers === 'BPM') { + this.isECM = false; + this.isBPM = true; + } else if (this.providers === 'ECM') { + this.isECM = true; + this.isBPM = false; + } else if (this.providers === 'ALL') { + this.isECM = true; + this.isBPM = true; + } } onLogin($event) { @@ -49,9 +86,13 @@ export class LoginDemoComponent { this.providers = 'ALL'; } else if (checked) { this.providers = 'ECM'; - } else { - this.providers = undefined; + } else if (!checked && this.providers === 'ALL') { + this.providers = 'BPM'; + } else if (!checked && this.providers === 'ECM') { + this.providers = ''; } + + localStorage.setItem('providers', this.providers); } toggleBPM(checked) { @@ -59,8 +100,25 @@ export class LoginDemoComponent { this.providers = 'ALL'; } else if (checked) { this.providers = 'BPM'; - } else { - this.providers = undefined; + } else if (!checked && this.providers === 'ALL') { + this.providers = 'ECM'; + } else if (!checked && this.providers === 'BPM') { + this.providers = ''; + } + + localStorage.setItem('providers', this.providers); + } + + toggleCSRF() { + this.disableCsrf = !this.disableCsrf; + } + + validateForm(event: any) { + let values = event.values; + if (values.controls['username'].value === this.blackListUsername) { + this.alfrescologin.addCustomFormError('username', 'This particular username has been blocked'); + event.preventDefault(); } } + } diff --git a/demo-shell-ng2/app/components/router/AuthRouterOutlet.ts b/demo-shell-ng2/app/components/router/AuthRouterOutlet.ts deleted file mode 100644 index 6b85b38075..0000000000 --- a/demo-shell-ng2/app/components/router/AuthRouterOutlet.ts +++ /dev/null @@ -1,53 +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 { ElementRef, DynamicComponentLoader, Directive, Attribute } from '@angular/core'; -import { Router, RouterOutlet, ComponentInstruction } from '@angular/router-deprecated'; -import { AlfrescoAuthenticationService } from 'ng2-alfresco-core/dist/ng2-alfresco-core'; - -@Directive({selector: 'auth-router-outlet'}) -export class AuthRouterOutlet extends RouterOutlet { - - publicRoutes: Array; - private router: Router; - - constructor(_elementRef: ElementRef, _loader: DynamicComponentLoader, - _parentRouter: Router, @Attribute('name') nameAttr: string, - private authentication: AlfrescoAuthenticationService) { - super(_elementRef, _loader, _parentRouter, nameAttr); - - this.router = _parentRouter; - this.publicRoutes = [ - '', 'login', 'signup' - ]; - } - - activate(instruction: ComponentInstruction) { - if (this._canActivate(instruction.urlPath)) { - return super.activate(instruction); - } - - this.router.navigate(['Login']); - } - - _canActivate(url) { - return this.publicRoutes.indexOf(url) !== -1 - || this.authentication.isLoggedIn(); - } -} -*/ 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 4d89fd0d8b..2533df52c2 100644 --- a/demo-shell-ng2/app/components/search/search-bar.component.html +++ b/demo-shell-ng2/app/components/search/search-bar.component.html @@ -1,5 +1,12 @@ - + +

Search results

- +
- +
diff --git a/demo-shell-ng2/app/components/search/search.component.ts b/demo-shell-ng2/app/components/search/search.component.ts index bf836b7783..a7306c954f 100644 --- a/demo-shell-ng2/app/components/search/search.component.ts +++ b/demo-shell-ng2/app/components/search/search.component.ts @@ -16,9 +16,6 @@ */ import { Component } from '@angular/core'; -import { AlfrescoContentService } from 'ng2-alfresco-core'; -import { ALFRESCO_SEARCH_DIRECTIVES } from 'ng2-alfresco-search'; -import { VIEWERCOMPONENT } from 'ng2-alfresco-viewer'; declare let __moduleName: string; @@ -47,24 +44,17 @@ declare let __moduleName: string; width: 100%; } } - `], - directives: [ ALFRESCO_SEARCH_DIRECTIVES, VIEWERCOMPONENT ] + `] }) export class SearchComponent { - previewContentUrl: string; - previewName: string; - previewMimeType: string; - previewActive: boolean = false; + fileShowed: boolean = false; fileNodeId: string; - constructor(public contentService: AlfrescoContentService) { - } - onFileClicked(event) { if (event.value.entry.isFile) { this.fileNodeId = event.value.entry.id; - this.previewActive = true; + this.fileShowed = true; } } } diff --git a/demo-shell-ng2/app/components/setting/setting.component.css b/demo-shell-ng2/app/components/setting/setting.component.css new file mode 100644 index 0000000000..da6c349ed8 --- /dev/null +++ b/demo-shell-ng2/app/components/setting/setting.component.css @@ -0,0 +1,26 @@ +.setting-card.mdl-card { + width: 320px; + height: 320px; +} +.setting-card > .mdl-card__title { + color: #fff; + background: bottom right 15% no-repeat #1fbcd2; +} + +.setting-card-padding { + width: 50%; + display: table-cell; + vertical-align: middle; + margin: 0; +} + +.setting-container { + display: table; + border-collapse: collapse; + border-spacing: 0; + width: 100%; +} + +.icon-margin { + margin-right: 9px; + } diff --git a/demo-shell-ng2/app/components/setting/setting.component.html b/demo-shell-ng2/app/components/setting/setting.component.html new file mode 100644 index 0000000000..60d372d4e8 --- /dev/null +++ b/demo-shell-ng2/app/components/setting/setting.component.html @@ -0,0 +1,30 @@ +
+
+
+
+
+

SETTINGS

+
+
+
+ ECM host URL configuration +
+ + +
+ BPM host URL configuration +
+ +
+
+
+
+
diff --git a/demo-shell-ng2/app/components/setting/setting.component.ts b/demo-shell-ng2/app/components/setting/setting.component.ts new file mode 100644 index 0000000000..d33e258c91 --- /dev/null +++ b/demo-shell-ng2/app/components/setting/setting.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 } from '@angular/core'; +import { + AlfrescoSettingsService +} from 'ng2-alfresco-core'; + +declare let __moduleName: string; + +@Component({ + moduleId: __moduleName, + selector: 'alfresco-setting-demo', + templateUrl: './setting.component.html', + styleUrls: ['./setting.component.css'] +}) +export class SettingComponent { + + ecmHost: string; + bpmHost: string; + + constructor(public alfrescoSettingsService: AlfrescoSettingsService) { + this.ecmHost = this.alfrescoSettingsService.ecmHost; + this.bpmHost = this.alfrescoSettingsService.bpmHost; + } + + 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); + } + +} diff --git a/demo-shell-ng2/app/components/tag/tag.component.ts b/demo-shell-ng2/app/components/tag/tag.component.ts new file mode 100644 index 0000000000..e362f692a7 --- /dev/null +++ b/demo-shell-ng2/app/components/tag/tag.component.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. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'alfresco-tag-demo', + template: ` +
+
+
+
+
List Tags ECM
+
+ Tag list By Node ID + +
+
+ ` +}) +export class TagComponent { + + nodeId: string = '74cd8a96-8a21-47e5-9b3b-a1b3e296787d'; +} diff --git a/demo-shell-ng2/app/components/webscript/webscript.component.ts b/demo-shell-ng2/app/components/webscript/webscript.component.ts index 839c42975f..9d00332036 100644 --- a/demo-shell-ng2/app/components/webscript/webscript.component.ts +++ b/demo-shell-ng2/app/components/webscript/webscript.component.ts @@ -16,29 +16,24 @@ */ 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 { diff --git a/demo-shell-ng2/app/css/app.css b/demo-shell-ng2/app/css/app.css index 3611cf6ae8..3656efa93b 100644 --- a/demo-shell-ng2/app/css/app.css +++ b/demo-shell-ng2/app/css/app.css @@ -35,3 +35,7 @@ body, html { text-align: center; position: relative; } + +._dialog_overlay { + position: static !important; +} diff --git a/demo-shell-ng2/app/img/admin.jpeg b/demo-shell-ng2/app/img/admin.jpeg deleted file mode 100644 index 0efd7dbd02..0000000000 Binary files a/demo-shell-ng2/app/img/admin.jpeg and /dev/null differ diff --git a/demo-shell-ng2/app/img/background.jpg b/demo-shell-ng2/app/img/background.jpg deleted file mode 100644 index 792f58a231..0000000000 Binary files a/demo-shell-ng2/app/img/background.jpg and /dev/null differ diff --git a/demo-shell-ng2/app/img/blank.gif b/demo-shell-ng2/app/img/blank.gif deleted file mode 100644 index e565824aaf..0000000000 Binary files a/demo-shell-ng2/app/img/blank.gif and /dev/null differ diff --git a/demo-shell-ng2/app/img/checklist.svg b/demo-shell-ng2/app/img/checklist.svg deleted file mode 100644 index 603b46fef5..0000000000 --- a/demo-shell-ng2/app/img/checklist.svg +++ /dev/null @@ -1,782 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/demo-shell-ng2/app/img/completed.svg b/demo-shell-ng2/app/img/completed.svg deleted file mode 100644 index 84f75fa8a2..0000000000 --- a/demo-shell-ng2/app/img/completed.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - diff --git a/demo-shell-ng2/app/img/createvisit.jpg b/demo-shell-ng2/app/img/createvisit.jpg deleted file mode 100644 index 7505e347e4..0000000000 Binary files a/demo-shell-ng2/app/img/createvisit.jpg and /dev/null differ diff --git a/demo-shell-ng2/app/img/users.png b/demo-shell-ng2/app/img/users.png deleted file mode 100644 index b1cca051a4..0000000000 Binary files a/demo-shell-ng2/app/img/users.png and /dev/null differ diff --git a/demo-shell-ng2/app/img/visit.jpg b/demo-shell-ng2/app/img/visit.jpg deleted file mode 100644 index b07d287404..0000000000 Binary files a/demo-shell-ng2/app/img/visit.jpg and /dev/null differ diff --git a/demo-shell-ng2/app/img/visitor.jpg b/demo-shell-ng2/app/img/visitor.jpg deleted file mode 100644 index 813426332c..0000000000 Binary files a/demo-shell-ng2/app/img/visitor.jpg and /dev/null differ diff --git a/demo-shell-ng2/app/js/Polyline.js b/demo-shell-ng2/app/js/Polyline.js new file mode 100644 index 0000000000..9983929cf1 --- /dev/null +++ b/demo-shell-ng2/app/js/Polyline.js @@ -0,0 +1,372 @@ +/** + * Class to generate polyline + * + * @author Dmitry Farafonov + */ + +var ANCHOR_TYPE= { + main: "main", + middle: "middle", + first: "first", + last: "last" +}; + +function Anchor(uuid, type, x, y) { + this.uuid = uuid; + this.x = x; + this.y = y; + this.type = (type == ANCHOR_TYPE.middle) ? ANCHOR_TYPE.middle : ANCHOR_TYPE.main; +}; +Anchor.prototype = { + uuid: null, + x: 0, + y: 0, + type: ANCHOR_TYPE.main, + isFirst: false, + isLast: false, + ndex: 0, + typeIndex: 0 +}; + +function Polyline(uuid, points, strokeWidth, paper) { + /* Array on coordinates: + * points: [{x: 410, y: 110}, 1 + * {x: 570, y: 110}, 1 2 + * {x: 620, y: 240}, 2 3 + * {x: 750, y: 270}, 3 4 + * {x: 650, y: 370}]; 4 + */ + this.points = points; + + /* + * path for graph + * [["M", x1, y1], ["L", x2, y2], ["C", ax, ay, bx, by, x3, y3], ["L", x3, y3]] + */ + this.path = []; + + this.anchors = []; + + if (strokeWidth) this.strokeWidth = strokeWidth; + + this.paper = paper; + + this.closePath = false; + + this.init(); +}; + +Polyline.prototype = { + id: null, + points: [], + path: [], + anchors: [], + strokeWidth: 1, + radius: 1, + showDetails: false, + paper: null, + element: null, + isDefaultConditionAvailable: false, + closePath: false, + + init: function(points){ + var linesCount = this.getLinesCount(); + if (linesCount < 1) + return; + + this.normalizeCoordinates(); + + // create anchors + + this.pushAnchor(ANCHOR_TYPE.first, this.getLine(0).x1, this.getLine(0).y1); + + for (var i = 1; i < linesCount; i++) + { + var line1 = this.getLine(i-1); + this.pushAnchor(ANCHOR_TYPE.main, line1.x2, line1.y2); + } + + this.pushAnchor(ANCHOR_TYPE.last, this.getLine(linesCount-1).x2, this.getLine(linesCount-1).y2); + + this.rebuildPath(); + }, + + normalizeCoordinates: function(){ + for(var i=0; i < this.points.length; i++){ + this.points[i].x = parseFloat(this.points[i].x); + this.points[i].y = parseFloat(this.points[i].y); + } + }, + + getLinesCount: function(){ + return this.points.length-1; + }, + _getLine: function(i){ + if (this.points.length > i && this.points[i]) { + return {x1: this.points[i].x, y1: this.points[i].y, x2: this.points[i+1].x, y2: this.points[i+1].y}; + } else { + return undefined; + } + }, + getLine: function(i){ + var line = this._getLine(i); + if (line != undefined) { + line.angle = this.getLineAngle(i); + } + return line; + }, + getLineAngle: function(i){ + var line = this._getLine(i); + return Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + }, + getLineLengthX: function(i){ + var line = this.getLine(i); + return (line.x2 - line.x1); + }, + getLineLengthY: function(i){ + var line = this.getLine(i); + return (line.y2 - line.y1); + }, + getLineLength: function(i){ + return Math.sqrt(Math.pow(this.getLineLengthX(i), 2) + Math.pow(this.getLineLengthY(i), 2)); + }, + + getAnchors: function(){ + return this.anchors; + }, + getAnchorsCount: function(type){ + if (!type) + return this.anchors.length; + else { + var count = 0; + for(var i=0; i < this.getAnchorsCount(); i++){ + var anchor = this.anchors[i]; + if (anchor.getType() == type) { + count++; + } + } + return count; + } + }, + + pushAnchor: function(type, x, y, index){ + if (type == ANCHOR_TYPE.first) { + index = 0; + typeIndex = 0; + } else if (type == ANCHOR_TYPE.last) { + index = this.getAnchorsCount(); + typeIndex = 0; + } else if (!index) { + index = this.anchors.length; + } else { + for(var i=0; i < this.getAnchorsCount(); i++){ + var anchor = this.anchors[i]; + if (anchor.index > index) { + anchor.index++; + anchor.typeIndex++; + } + } + } + + var anchor = new Anchor(this.id, ANCHOR_TYPE.main, x, y, index, typeIndex); + + this.anchors.push(anchor); + }, + + getAnchor: function(position){ + return this.anchors[position]; + }, + + getAnchorByType: function(type, position){ + if (type == ANCHOR_TYPE.first) + return this.anchors[0]; + if (type == ANCHOR_TYPE.last) + return this.anchors[this.getAnchorsCount()-1]; + + for(var i=0; i < this.getAnchorsCount(); i++){ + var anchor = this.anchors[i]; + if (anchor.type == type) { + if( position == anchor.position) + return anchor; + } + } + return null; + }, + + addNewPoint: function(position, x, y){ + // + for(var i = 0; i < this.getLinesCount(); i++){ + var line = this.getLine(i); + if (x > line.x1 && x < line.x2 && y > line.y1 && y < line.y2) { + this.points.splice(i+1,0,{x: x, y: y}); + break; + } + } + + this.rebuildPath(); + }, + + rebuildPath: function(){ + var path = []; + + for(var i = 0; i < this.getAnchorsCount(); i++){ + var anchor = this.getAnchor(i); + + var pathType = ""; + if (i == 0) + pathType = "M"; + else + pathType = "L"; + + // TODO: save previous points and calculate new path just if points are updated, and then save currents values as previous + + var targetX = anchor.x, targetY = anchor.y; + if (i>0 && i < this.getAnchorsCount()-1) { + // get new x,y + var cx = anchor.x, cy = anchor.y; + + // pivot point of prev line + var AO = this.getLineLength(i-1); + if (AO < this.radius) { + AO = this.radius; + } + + this.isDefaultConditionAvailable = (this.isDefaultConditionAvailable || (i == 1 && AO > 10)); + + var ED = this.getLineLengthY(i-1) * this.radius / AO; + var OD = this.getLineLengthX(i-1) * this.radius / AO; + targetX = anchor.x - OD; + targetY = anchor.y - ED; + + if (AO < 2*this.radius && i>1) { + targetX = anchor.x - this.getLineLengthX(i-1)/2; + targetY = anchor.y - this.getLineLengthY(i-1)/2;; + } + + // pivot point of next line + var AO = this.getLineLength(i); + if (AO < this.radius) { + AO = this.radius; + } + var ED = this.getLineLengthY(i) * this.radius / AO; + var OD = this.getLineLengthX(i) * this.radius / AO; + var nextSrcX = anchor.x + OD; + var nextSrcY = anchor.y + ED; + + if (AO < 2*this.radius && i 10)); + } + + // anti smoothing + if (this.strokeWidth%2 == 1) { + targetX += 0.5; + targetY += 0.5; + } + + path.push([pathType, targetX, targetY]); + + if (i>0 && i < this.getAnchorsCount()-1) { + path.push(["C", ax, ay, bx, by, zx, zy]); + } + } + + if (this.closePath) + { + path.push(["Z"]); + } + + this.path = path; + }, + + transform: function(transformation) + { + this.element.transform(transformation); + }, + attr: function(attrs) + { + // TODO: foreach and set each + this.element.attr(attrs); + } +}; + +function Polygone(points, strokeWidth) { + /* Array on coordinates: + * points: [{x: 410, y: 110}, 1 + * {x: 570, y: 110}, 1 2 + * {x: 620, y: 240}, 2 3 + * {x: 750, y: 270}, 3 4 + * {x: 650, y: 370}]; 4 + */ + this.points = points; + + /* + * path for graph + * [["M", x1, y1], ["L", x2, y2], ["C", ax, ay, bx, by, x3, y3], ["L", x3, y3]] + */ + this.path = []; + + this.anchors = []; + + if (strokeWidth) this.strokeWidth = strokeWidth; + + this.closePath = true; + this.init(); +}; + + +/* + * Poligone is inherited from Poliline: draws closedPath of polyline + */ + +var Foo = function () { }; +Foo.prototype = Polyline.prototype; + +Polygone.prototype = new Foo(); + +Polygone.prototype.rebuildPath = function(){ + var path = []; + for(var i = 0; i < this.getAnchorsCount(); i++){ + var anchor = this.getAnchor(i); + + var pathType = ""; + if (i == 0) + pathType = "M"; + else + pathType = "L"; + + var targetX = anchor.x, targetY = anchor.y; + + // anti smoothing + if (this.strokeWidth%2 == 1) { + targetX += 0.5; + targetY += 0.5; + } + + path.push([pathType, targetX, targetY]); + } + if (this.closePath) + path.push(["Z"]); + + this.path = path; +}; \ No newline at end of file diff --git a/demo-shell-ng2/app/js/xml2json.js b/demo-shell-ng2/app/js/xml2json.js deleted file mode 100644 index 09193e6fcd..0000000000 --- a/demo-shell-ng2/app/js/xml2json.js +++ /dev/null @@ -1,178 +0,0 @@ -/* This work is licensed under Creative Commons GNU LGPL License. - - - - License: http://creativecommons.org/licenses/LGPL/2.1/ - Version: 0.9 - Author: Stefan Goessner/2006 - Web: http://goessner.net/ -*/ -var parseXml; - -if (typeof window.DOMParser != "undefined") { - parseXml = function(xmlStr) { - return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml"); - }; -} else if (typeof window.ActiveXObject != "undefined" && - new window.ActiveXObject("Microsoft.XMLDOM")) { - parseXml = function(xmlStr) { - var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM"); - xmlDoc.async = "false"; - xmlDoc.loadXML(xmlStr); - return xmlDoc; - }; -} else { - throw new Error("No XML parser found"); -} - - - -function xml2json(xml, tab) { - var X = { - toObj: function(xml) { - var o = {}; - if (xml.nodeType==1) { // element node .. - if (xml.attributes.length) // element with attributes .. - for (var i=0; i 1) - o = X.escape(X.innerXml(xml)); - else - for (var n=xml.firstChild; n; n=n.nextSibling) - o["#cdata"] = X.escape(n.nodeValue); - } - } - if (!xml.attributes.length && !xml.firstChild) o = null; - } - else if (xml.nodeType==9) { // document.node - o = X.toObj(xml.documentElement); - } - else - alert("unhandled node type: " + xml.nodeType); - return o; - }, - toJson: function(o, name, ind) { - var json = name ? ("\""+name+"\"") : ""; - if (o instanceof Array) { - for (var i=0,n=o.length; i 1 ? ("\n"+ind+"\t"+o.join(",\n"+ind+"\t")+"\n"+ind) : o.join("")) + "]"; - } - else if (o == null) - json += (name&&":") + "null"; - else if (typeof(o) == "object") { - var arr = []; - for (var m in o) - arr[arr.length] = X.toJson(o[m], m, ind+"\t"); - json += (name?":{":"{") + (arr.length > 1 ? ("\n"+ind+"\t"+arr.join(",\n"+ind+"\t")+"\n"+ind) : arr.join("")) + "}"; - } - else if (typeof(o) == "string") - json += (name&&":") + "\"" + o.toString() + "\""; - else - json += (name&&":") + o.toString(); - return json; - }, - innerXml: function(node) { - var s = "" - if ("innerHTML" in node) - s = node.innerHTML; - else { - var asXml = function(n) { - var s = ""; - if (n.nodeType == 1) { - s += "<" + n.nodeName; - for (var i=0; i"; - } - else - s += "/>"; - } - else if (n.nodeType == 3) - s += n.nodeValue; - else if (n.nodeType == 4) - s += ""; - return s; - }; - for (var c=node.firstChild; c; c=c.nextSibling) - s += asXml(c); - } - return s; - }, - escape: function(txt) { - return txt.replace(/[\\]/g, "\\\\") - .replace(/[\"]/g, '\\"') - .replace(/[\n]/g, '\\n') - .replace(/[\r]/g, '\\r'); - }, - removeWhite: function(e) { - e.normalize(); - for (var n = e.firstChild; n; ) { - if (n.nodeType == 3) { // text node - if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { // pure whitespace text node - var nxt = n.nextSibling; - e.removeChild(n); - n = nxt; - } - else - n = n.nextSibling; - } - else if (n.nodeType == 1) { // element node - X.removeWhite(n); - n = n.nextSibling; - } - else // any other node - n = n.nextSibling; - } - return e; - } - }; - xml = parseXml(xml); - if (xml.nodeType == 9) // document node - xml = xml.documentElement; - var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t"); - return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}"; -} \ No newline at end of file diff --git a/demo-shell-ng2/app/main.ts b/demo-shell-ng2/app/main.ts index 31c4a652a0..d585676518 100644 --- a/demo-shell-ng2/app/main.ts +++ b/demo-shell-ng2/app/main.ts @@ -15,21 +15,8 @@ * limitations under the License. */ -import { bootstrap } from '@angular/platform-browser-dynamic'; -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 { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app.module'; -import { appRouterProviders } from './app.routes'; - -bootstrap(AppComponent, [ - appRouterProviders, - HTTP_PROVIDERS, - ALFRESCO_CORE_PROVIDERS, - ALFRESCO_SEARCH_PROVIDERS, - UploadService, - ATIVITI_FORM_PROVIDERS -]).catch(err => console.error(err)); +const platform = platformBrowserDynamic(); +platform.bootstrapModule(AppModule); diff --git a/demo-shell-ng2/i18n/en.json b/demo-shell-ng2/i18n/en.json index 6f006a8158..1993786440 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": "mario", + "CREATED_BY": "Created by", "CREATED_ON": "Created on" }, "ACTIONS": { @@ -57,7 +57,7 @@ "LOGIN-SUCCESS": "Login successful" }, "BUTTON": { - "LOGIN": "MARIO" + "LOGIN": "Login" }, "ACTION": { "HELP": "NEED HELP?", diff --git a/demo-shell-ng2/index.html b/demo-shell-ng2/index.html index 3fbdbbf291..aec2e4da25 100644 --- a/demo-shell-ng2/index.html +++ b/demo-shell-ng2/index.html @@ -9,26 +9,44 @@ + - + - + + + + + + + + + + + + + + - - - + + + + + + + diff --git a/demo-shell-ng2/package.json b/demo-shell-ng2/package.json index 5524a4ef30..1fa8e2966f 100644 --- a/demo-shell-ng2/package.json +++ b/demo-shell-ng2/package.json @@ -1,18 +1,18 @@ { "name": "Alfresco-Angular2-Demo", "description": "Demo shell for Alfresco Angular2 components", - "version": "0.3.0", + "version": "0.4.0", "author": "Alfresco Software, Ltd.", "scripts": { - "postinstall": "typings install", - "clean": "rimraf node_modules typings", + "clean": "npm install rimraf && rimraf dist node_modules typings", "build": "npm run tslint && npm run tsc && npm run licensecheck", - "start": "npm run build && concurrently \"npm run tsc:w\" \"npm run serve\" ", + "start": "npm run build && npm run serve", + "start:dev": "npm run build && concurrently \"npm run tsc:w\" \"npm run serve:dev\" ", "aws": "node app.js", "tsc": "tsc", "tsc:w": "tsc -w", - "serve": "wsrv -o -s -l -p 3000 -a 0.0.0.0 -x ./server/versions.js", - "typings": "typings install", + "serve": "wsrv -O http://localhost:3000 -s -p 3000 -a 0.0.0.0 -x ./server/versions.js", + "serve:dev": "wsrv -O http://localhost:3000 -s -l -p 3000 -a 0.0.0.0 -x ./server/versions.js", "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'app/{,**/}**.ts'", "licensecheck": "license-check" }, @@ -53,53 +53,66 @@ "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", - "alfresco-js-api": "^0.3.0", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "@angular/router": "3.0.0", + "@angular/upgrade": "2.0.0", + "@types/node": "^6.0.42", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", "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", + "zone.js": "^0.6.23", + "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", + "material-design-lite": "1.2.1", + "ng2-translate": "2.5.0", + "pdfjs-dist": "1.5.404", "flag-icon-css": "2.3.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" + "intl": "1.2.4", + "moment": "2.15.1", + "chart.js": "^2.1.4", + "ng2-charts": "1.1.0", + "raphael": "^2.2.6", + "md-date-time-picker": "^2.2.0", + "alfresco-js-api": "^0.4.0", + "ng2-activiti-analytics": "0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-alfresco-datatable": "0.4.0", + "ng2-alfresco-documentlist": "0.4.0", + "ng2-alfresco-login": "0.4.0", + "ng2-alfresco-search": "0.4.0", + "ng2-alfresco-upload": "0.4.0", + "ng2-alfresco-viewer": "0.4.0", + "ng2-activiti-form": "0.4.0", + "ng2-activiti-tasklist": "0.4.0", + "ng2-alfresco-userinfo": "0.4.0", + "ng2-activiti-processlist": "0.4.0", + "ng2-alfresco-webscript": "0.4.0", + "ng2-alfresco-tag": "0.4.0", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1" }, "devDependencies": { - "concurrently": "2.0.0", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", "license-check": "1.1.5", "mime": "^1.3.4", - "tslint": "3.8.1", - "typescript": "1.8.10", - "typings": "1.0.4", - "wsrv": "0.1.4" + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" }, "license-check-config": { "src": [ "./app/**/*.js", - "!./app/js/xml2json.js" + "!./app/js/*.js" ], "path": "assets/license_header.txt", "blocking": true, diff --git a/demo-shell-ng2/systemjs.config.js b/demo-shell-ng2/systemjs.config.js index 782acb4d85..94daf63400 100644 --- a/demo-shell-ng2/systemjs.config.js +++ b/demo-shell-ng2/systemjs.config.js @@ -2,78 +2,75 @@ * 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': 'app', // 'dist', - '@angular': 'node_modules/@angular', - 'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api', - 'rxjs': 'node_modules/rxjs', +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'app', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'moment': 'npm:moment/min/moment.min.js', + 'ng2-charts' : 'npm:ng2-charts', + 'ng2-translate': 'npm:ng2-translate', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'npm:ng2-alfresco-datatable/dist', + 'ng2-alfresco-documentlist': 'npm:ng2-alfresco-documentlist/dist', + 'ng2-alfresco-login': 'npm:ng2-alfresco-login/dist', + 'ng2-alfresco-search': 'npm:ng2-alfresco-search/dist', + 'ng2-alfresco-upload': 'npm:ng2-alfresco-upload/dist', + 'ng2-activiti-form': 'npm:ng2-activiti-form/dist', + 'ng2-alfresco-viewer': 'npm:ng2-alfresco-viewer/dist', + 'ng2-alfresco-webscript': 'npm:ng2-alfresco-webscript/dist', + 'ng2-alfresco-tag': 'npm:ng2-alfresco-tag/dist', + 'ng2-activiti-tasklist': 'npm:ng2-activiti-tasklist/dist', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-activiti-processlist': 'npm:ng2-activiti-processlist/dist', + 'ng2-alfresco-userinfo': 'npm:ng2-alfresco-userinfo/dist', + 'ng2-activiti-analytics': 'npm:ng2-activiti-analytics/dist', + 'ng2-activiti-diagrams': 'npm:ng2-activiti-diagrams/dist' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-charts': { defaultExtension: 'js' }, - '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-alfresco-documentlist': 'node_modules/ng2-alfresco-documentlist/dist', - '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-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 = { - '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-alfresco-documentlist': { main: 'index.js', defaultExtension: 'js'}, - '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-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', - '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); - - // No umd for router yet - packages['@angular/router'] = { main: 'index.js', defaultExtension: 'js' }; - - var config = { - map: map, - packages: packages - }; - System.config(config); + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-datatable': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-documentlist': { main: './index.js', defaultExtension: 'js'}, + '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-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'}, + 'ng2-alfresco-tag': { main: './index.js', defaultExtension: 'js'}, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-userinfo': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-analytics': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-diagrams': { main: './index.js', defaultExtension: 'js'} + } + }); })(this); diff --git a/demo-shell-ng2/tsconfig.json b/demo-shell-ng2/tsconfig.json index 0cd78ac35d..b48f1186e3 100644 --- a/demo-shell-ng2/tsconfig.json +++ b/demo-shell-ng2/tsconfig.json @@ -7,7 +7,8 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "removeComments": false, - "noImplicitAny": false + "noImplicitAny": false, + "types": ["core-js", "jasmine"] }, "exclude": [ "dist", diff --git a/demo-shell-ng2/tslint.json b/demo-shell-ng2/tslint.json index e550ac11d4..4b43d6658e 100644 --- a/demo-shell-ng2/tslint.json +++ b/demo-shell-ng2/tslint.json @@ -10,8 +10,7 @@ "class-name": true, "comment-format": [ true, - "check-space", - "check-lowercase" + "check-space" ], "curly": true, "eofline": true, diff --git a/demo-shell-ng2/typings.json b/demo-shell-ng2/typings.json deleted file mode 100644 index d8954c2485..0000000000 --- a/demo-shell-ng2/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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/demo-shell-ng2/wsrv-config.json b/demo-shell-ng2/wsrv-config.json index 69fa7707ea..bacfda5339 100644 --- a/demo-shell-ng2/wsrv-config.json +++ b/demo-shell-ng2/wsrv-config.json @@ -8,6 +8,7 @@ "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}" + "node_modules/ng2-activiti-form/dist/**/*.{html,css,js}", + "node_modules/ng2-activiti-tasklist/dist/**/*.{html,css,js}" ] } diff --git a/ng2-components/README.md b/ng2-components/README.md index 9e95e5b699..4d8702dd16 100644 --- a/ng2-components/README.md +++ b/ng2-components/README.md @@ -43,13 +43,49 @@ - [Login](ng2-alfresco-login/README.md) - [Upload](ng2-alfresco-upload/README.md) - [Webscript Viewer](ng2-alfresco-webscript/README.md) +- [Tag list and controls](ng2-alfresco-tag/README.md) ## BPM components -- [Activiti Forms](ng2-activiti-form/README.md) -- [Activiti Process](ng2-activiti-processlist/README.md) -- [Activiti Tasks](ng2-activiti-tasklist/README.md) +- [TaskList](ng2-activiti-tasklist/README.md) +- [ProcessList](ng2-activiti-processlist/README.md) +- [Form](ng2-activiti-form/README.md) You can browse all the components at the following address: http://devproducts.alfresco.com/ + +## How to test a change to a generic component in its own demo + +Let's suppose that for some reason you have changed a component and you want to test this changes. +The example is based on the ng2-alfresco-login component, but you can use the same steps for any component. + + +1. Move inside the component folder and link it. +```sh + +cd ng2-alfresco-login +npm link + +``` + +2. Build the component with the watcher enabled. +```sh + +npm run build:w + +``` + +3. From another terminal move inside the demo sub folder and link the component to the local node_modules folder. +```sh + +cd demo +npm link ng2-alfresco-login + +``` + +4. Start the demo project. +```sh + +npm run start +``` diff --git a/ng2-components/ng2-activiti-analytics/.editorconfig b/ng2-components/ng2-activiti-analytics/.editorconfig new file mode 100644 index 0000000000..75a2477db7 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/.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-analytics/.gitignore b/ng2-components/ng2-activiti-analytics/.gitignore new file mode 100644 index 0000000000..fe20e77f42 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/.gitignore @@ -0,0 +1,15 @@ +npm-debug.log +node_modules +.idea +typings +coverage +dist +src/**/*.js +src/**/*.js.map + +demo/**/*.js +demo/**/*.js.map +demo/**/*.d.ts +index.js +index.js.map +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-analytics/.npmignore b/ng2-components/ng2-activiti-analytics/.npmignore new file mode 100644 index 0000000000..c5ca623298 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/.npmignore @@ -0,0 +1,15 @@ +npm-debug.log +.idea + +coverage/ +node_modules +typings/ +fonts/ + +/.editorconfig +/.travis.yml +/*.js +/*.json +/*.ts +/*.js.map +/.npmignore diff --git a/ng2-components/ng2-activiti-analytics/LICENSE b/ng2-components/ng2-activiti-analytics/LICENSE new file mode 100644 index 0000000000..430d42bbea --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/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-analytics/README.md b/ng2-components/ng2-activiti-analytics/README.md new file mode 100644 index 0000000000..166bf6e17c --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/README.md @@ -0,0 +1,317 @@ +# Activiti Analytics Component for Angular 2 +

+ + travis
+    Status + + + travis
+    Status + + + Coverage Status + + + npm downloads + + + license + + + alfresco component + + + angular 2 + + + typescript + + + node version + +

+ +## 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 + +Follow the 3 steps below: + +1. Npm + + ```sh + npm install ng2-activiti-analytics --save + ``` + +2. Html + + Include these dependencies in your index.html page: + + ```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + +3. SystemJs + + Add the following components to your systemjs.config.js file: + + - moment + - ng2-charts + - ng2-translate + - alfresco-js-api + - ng2-alfresco-core + - ng2-activiti-diagrams + - ng2-activiti-analytics + + Please refer to the following example file: [systemjs.config.js](demo/systemjs + .config.js) . + +## Basic usage example Activiti Analytics List + +The component shows the list of all the available reports + +```html + +``` + +Usage example of this component : + +**main.ts** +```ts + +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { AnalyticsModule } from 'ng2-activiti-analytics'; + +@Component({ + selector: 'activiti-analytics-demo', + template: ` +
+
+
+ +
+
+
` +}) + +export class AnalyticsDemoComponent { + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = 'http://localhost:9999'; + + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + }, + error => { + console.log(error); + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + AnalyticsModule + ], + declarations: [ AnalyticsDemoComponent ], + bootstrap: [ AnalyticsDemoComponent ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); + + +``` + +#### Events + +| Name | Description | +| --- | --- | +|`onSuccess`| The event is emitted when the report list are loaded | +|`onError`| The event is emitted when an error occur during the loading | +|`reportClick`| The event is emitted when the report in the list is selected | + +#### Options + +No options. + +## Basic usage example Activiti Analytics + +The component shows the charts related to the reportId passed as input + +```html + +``` + +Example of an App that use Activiti Analytics component : + +**main.ts** +```ts + +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { AnalyticsModule } from 'ng2-activiti-analytics'; + +@Component({ + selector: 'activiti-analytics-demo', + template: ` +
+
+
+ +
+
+
` +}) + +export class AnalyticsDemoComponent { + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = 'http://localhost:9999'; + + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + }, + error => { + console.log(error); + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + AnalyticsModule + ], + declarations: [ AnalyticsDemoComponent ], + bootstrap: [ AnalyticsDemoComponent ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); + + +``` + +#### Events + +| Name | Description | +| --- | --- | +|`onSuccess` | The event is emitted when the report parameters are loaded | +|`onError` | The event is emitted when an error occur during the loading | + +#### Options + +| Name | Description | +| --- | --- | +|`appId` | The application id | +|`reportId` | The report id | +|`debug` | Flag to enable or disable the Form values in the console log | + +## 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 +``` + +## Demo + +If you want have a demo of how the component works, please check the demo folder : + +```sh +cd demo +npm install +npm start +``` + +## NPM scripts + +| Command | Description | +| --- | --- | +| npm run build | Build component | +| npm run build:w | Build component and keep watching the changes | +| npm run test | Run unit tests in the console | +| npm run test-browser | Run unit tests in the browser +| npm run coverage | Run unit tests and display code coverage report | + +## License + +[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE) diff --git a/ng2-components/ng2-activiti-analytics/assets/license_header.txt b/ng2-components/ng2-activiti-analytics/assets/license_header.txt new file mode 100644 index 0000000000..83fd1531a3 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/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-analytics/demo/.gitignore b/ng2-components/ng2-activiti-analytics/demo/.gitignore new file mode 100644 index 0000000000..25beca4c27 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/.gitignore @@ -0,0 +1,6 @@ +node_modules +.idea +coverage +dist +typings +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-analytics/demo/.npmignore b/ng2-components/ng2-activiti-analytics/demo/.npmignore new file mode 100644 index 0000000000..c51c008259 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/.npmignore @@ -0,0 +1,3 @@ +node_modules +dist +typings \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/demo/README.md b/ng2-components/ng2-activiti-analytics/demo/README.md new file mode 100644 index 0000000000..cd01855a98 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/README.md @@ -0,0 +1,13 @@ +# Activiti Analytics demo + +Install: + +``` +npm install +``` + +Run the project: + +``` +npm start +``` \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/demo/index.html b/ng2-components/ng2-activiti-analytics/demo/index.html new file mode 100644 index 0000000000..05273a659d --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/index.html @@ -0,0 +1,59 @@ + + + + + + Alfresco Angular 2 Activiti Analytics - Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ng2-components/ng2-activiti-analytics/demo/package.json b/ng2-components/ng2-activiti-analytics/demo/package.json new file mode 100644 index 0000000000..c52e4e2f55 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/package.json @@ -0,0 +1,74 @@ +{ + "name": "ng2-activiti-diagrams-demo", + "description": "Alfresco Angular2 Diagrams Component - Demo", + "version": "0.1.0", + "author": "Alfresco Software, Ltd.", + "main": "index.js", + "scripts": { + "clean": "npm install rimraf && rimraf dist node_modules typings dist", + "postinstall": "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 && tsc", + "build:w": "npm run tslint && rimraf dist && tsc -w", + "tsc": "tsc", + "tsc:w": "tsc -w", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Maurizio Vitale", + "email": "maurizio.vitale@alfresco.com" + } + ], + "keywords": [ + "ng2", + "angular", + "angular2", + "activiti", + "activiti-diagrams" + ], + "dependencies": { + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "chart.js": "^2.1.4", + "md-date-time-picker": "^2.2.0", + "ng2-charts": "1.1.0", + "moment": "2.15.1", + "raphael": "^2.2.6", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-activiti-diagrams": "0.4.0", + "ng2-activiti-analytics": "^0.4.0" + }, + "devDependencies": { + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", + "rimraf": "2.5.2", + "tslint": "^3.8.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" + } +} diff --git a/ng2-components/ng2-activiti-analytics/demo/src/main.ts b/ng2-components/ng2-activiti-analytics/demo/src/main.ts new file mode 100644 index 0000000000..b897f7cb7b --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/src/main.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 { NgModule, Component, OnInit } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { AnalyticsModule } from 'ng2-activiti-analytics'; + +@Component({ + selector: 'alfresco-app-demo', + template: ` +
+
+
+

+
+ Authentication failed to ip {{ host }} with user: admin, admin, you can still try to add a valid ticket to perform + operations. +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
` +}) + +export class AnalyticsDemoComponent implements OnInit { + + appId: number; + + report: any; + + authenticated: boolean; + + host: string = 'http://localhost:9999'; + + ticket: string; + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = this.host; + settingsService.setProviders('BPM'); + + if (this.authService.getTicketBpm()) { + this.ticket = this.authService.getTicketBpm(); + } + } + + onReportClick(event: any) { + this.report = event; + } + + public updateTicket(): void { + localStorage.setItem('ticket-BPM', this.ticket); + } + + public updateHost(): void { + this.settingsService.bpmHost = this.host; + this.login(); + } + + public ngOnInit(): void { + this.login(); + } + + login() { + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + this.ticket = this.authService.getTicketBpm(); + this.authenticated = true; + }, + error => { + console.log(error); + this.authenticated = false; + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + AnalyticsModule + ], + declarations: [AnalyticsDemoComponent], + bootstrap: [AnalyticsDemoComponent] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/ng2-components/ng2-activiti-analytics/demo/systemjs.config.js b/ng2-components/ng2-activiti-analytics/demo/systemjs.config.js new file mode 100644 index 0000000000..54b1de6634 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/systemjs.config.js @@ -0,0 +1,51 @@ +/** + * System configuration for Angular 2 samples + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'moment': 'npm:moment/min/moment.min.js', + 'ng2-charts': 'npm:ng2-charts', + 'ng2-translate': 'npm:ng2-translate', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-activiti-diagrams': 'npm:ng2-activiti-diagrams/dist', + 'ng2-activiti-analytics': 'npm:ng2-activiti-analytics/dist' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-charts': { main: 'ng2-charts.js', defaultExtension: 'js'}, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-diagrams': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-analytics': { main: './index.js', defaultExtension: 'js'} + } + }); +})(this); diff --git a/ng2-components/ng2-activiti-analytics/demo/tsconfig.json b/ng2-components/ng2-activiti-analytics/demo/tsconfig.json new file mode 100644 index 0000000000..7be35bfec8 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/demo/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "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", + "types": ["core-js", "jasmine", "node"] + }, + "exclude": [ + "demo", + "node_modules", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-analytics/demo/tslint.json b/ng2-components/ng2-activiti-analytics/demo/tslint.json new file mode 100644 index 0000000000..8c9703b9de --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/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, + 180 + ], + "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": true, + "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-analytics/index.ts b/ng2-components/ng2-activiti-analytics/index.ts new file mode 100644 index 0000000000..c56527241d --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/index.ts @@ -0,0 +1,74 @@ +/*! + * @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 { NgModule, ModuleWithProviders } from '@angular/core'; +import { CoreModule } from 'ng2-alfresco-core'; +import { DiagramsModule } from 'ng2-activiti-diagrams'; + +import { AnalyticsReportListComponent } from './src/components/analytics-report-list.component'; +import { AnalyticsReportParametersComponent } from './src/components/analytics-report-parameters.component'; +import { AnalyticsComponent } from './src/components/analytics.component'; +import { AnalyticsReportHeatMapComponent } from './src/components/analytics-report-heat-map.component'; +import { AnalyticsService } from './src/services/analytics.service'; +import { CHART_DIRECTIVES } from 'ng2-charts/ng2-charts'; + +import { WIDGET_DIRECTIVES } from './src/components/widgets/index'; + +export * from './src/components/analytics.component'; +export * from './src/components/analytics-report-list.component'; +export * from './src/components/analytics-report-parameters.component'; +export * from './src/services/analytics.service'; +export * from './src/components/widgets/index'; + +export const ANALYTICS_DIRECTIVES: any[] = [ + AnalyticsComponent, + AnalyticsReportListComponent, + AnalyticsReportParametersComponent, + AnalyticsReportHeatMapComponent, + WIDGET_DIRECTIVES +]; + +export const ANALYTICS_PROVIDERS: any[] = [ + AnalyticsService +]; + +@NgModule({ + imports: [ + CoreModule, + DiagramsModule + ], + declarations: [ + ...ANALYTICS_DIRECTIVES, + ...CHART_DIRECTIVES + ], + providers: [ + ...ANALYTICS_PROVIDERS + ], + exports: [ + ...ANALYTICS_DIRECTIVES + ] +}) +export class AnalyticsModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: AnalyticsModule, + providers: [ + ...ANALYTICS_PROVIDERS + ] + }; + } +} diff --git a/ng2-components/ng2-activiti-analytics/karma-test-shim.js b/ng2-components/ng2-activiti-analytics/karma-test-shim.js new file mode 100644 index 0000000000..3b4258d80a --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/karma-test-shim.js @@ -0,0 +1,114 @@ +// Tun on full stack traces in errors to help debugging +Error.stackTraceLimit = Infinity; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +__karma__.loaded = function() {}; + +var builtPath = '/base/dist/'; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +var paths = { + // paths serve as alias + 'npm:': 'base/node_modules/' +}; + +var map = { + 'app': 'base/dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // testing + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + 'ng2-charts' : 'npm:ng2-charts', + 'md-date-time-picker' : 'npm:md-date-time-picker', + 'moment' : 'npm:moment/min/moment.min.js', + + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-activiti-analytics': 'npm:ng2-activiti-analytics/dist', + 'ng2-activiti-diagrams': 'npm:ng2-activiti-diagrams/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist' +}; + +var packages = { + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-charts': { defaultExtension: 'js' }, + 'md-date-time-picker': { defaultExtension: 'js' }, + 'moment': { defaultExtension: 'js' }, + + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-activiti-analytics': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-diagrams': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'} +}; + +var config = { + paths: paths, + map: map, + packages: packages +}; + +System.config(config); + +System.import('@angular/core/testing') + .then(initTestBed) + .then(initTesting); + +function initTestBed(){ + return Promise.all([ + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') + ]) + .then(function (providers) { + var coreTesting = providers[0]; + var browserTesting = providers[1]; + + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); + }) +} + +// Import all spec files and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { + return System.import(moduleName); + }) + ) + .then(__karma__.start, __karma__.error); +} diff --git a/ng2-components/ng2-activiti-analytics/karma.conf.js b/ng2-components/ng2-activiti-analytics/karma.conf.js new file mode 100644 index 0000000000..496c6a6e9b --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/karma.conf.js @@ -0,0 +1,126 @@ +'use strict'; + +module.exports = function (config) { + var configuration = { + basePath: '.', + + frameworks: ['jasmine-ajax', 'jasmine'], + + files: [ + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', + + // Polyfills + 'node_modules/core-js/client/shim.js', + 'node_modules/reflect-metadata/Reflect.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false}, + + // Paths loaded via module imports: + // Angular itself + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, + + 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + 'node_modules/moment/min/moment.min.js', + 'node_modules/md-date-time-picker/dist/js/mdDateTimePicker.js', + 'node_modules/chart.js/dist/Chart.bundle.min.js', + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, + + 'karma-test-shim.js', + + // 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}, + + // ng2-components + { pattern: 'node_modules/ng2-alfresco-core/dist/**/*.*', included: false, served: true, watched: false }, + { pattern: 'node_modules/ng2-activiti-diagrams/dist/**/*.*', included: false, served: true, watched: false }, + { pattern: 'node_modules/ng2-charts/**/*.js', included: false, served: true, watched: false }, + { pattern: 'node_modules/md-date-time-picker/**/*.js', included: false, served: true, watched: false }, + { pattern: 'node_modules/moment/**/*.js', included: false, served: true, watched: false }, + + // 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|index|*mock|*model).js': 'coverage' + }, + + coverageReporter: { + includeAllSources: true, + 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-analytics/package.json b/ng2-components/ng2-activiti-analytics/package.json new file mode 100644 index 0000000000..47b104b9df --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/package.json @@ -0,0 +1,110 @@ +{ + "name": "ng2-activiti-analytics", + "description": "Activiti Angular2 Analytics Component", + "version": "0.4.0", + "author": "Alfresco Software, Ltd.", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "scripts": { + "clean": "npm install rimraf && rimraf dist node_modules typings", + "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 ng2-activiti-diagrams" + }, + "contributors": [ + { + "name": "Mario Romano", + "email": "mario.romnao@alfresco.com" + }, + { + "name": "Maurizio Vitale", + "email": "maurizio.vitale84@gmail.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/Alfresco/alfresco-ng2-components.git" + }, + "bugs": { + "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" + }, + "dependencies": { + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "chart.js": "^2.1.4", + "md-date-time-picker": "^2.2.0", + "ng2-charts": "1.1.0", + "moment": "2.15.1", + "raphael": "^2.2.6", + + "alfresco-js-api": "^0.4.0", + "ng2-translate": "2.5.0", + "ng2-alfresco-core": "0.4.0", + "ng2-activiti-diagrams": "0.4.0" + }, + "devDependencies": { + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", + "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-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", + "remap-istanbul": "0.6.3", + "rimraf": "2.5.2", + "traceur": "0.0.91", + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" + }, + "keywords": [ + "tag", + "alfresco-component" + ], + "license-check-config": { + "src": [ + "./dist/**/*.js" + ], + "path": "assets/license_header.txt", + "blocking": true, + "logInfo": false, + "logError": true + }, + "license": "Apache-2.0" +} diff --git a/ng2-components/ng2-activiti-analytics/src/assets/analyticsComponent.mock.ts b/ng2-components/ng2-activiti-analytics/src/assets/analyticsComponent.mock.ts new file mode 100644 index 0000000000..1990127265 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/assets/analyticsComponent.mock.ts @@ -0,0 +1,113 @@ +/*! + * @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 var chartProcessDefOverview = { + 'elements': [{ + 'id': 'id1585876275153', + 'type': 'table', + 'rows': [ + ['__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-TOTAL-PROCESS-DEFINITIONS', '9'], + ['__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-TOTAL-PROCESS-INSTANCES', '41'], + ['__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-ACTIVE-PROCESS-INSTANCES', '3'], + ['__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-COMPLETED-PROCESS-INSTANCES', '38'] + ] + }, { + 'id': 'id1585876413072', + 'type': 'pieChart', + 'title': 'Total process instances overview', + 'titleKey': 'REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.PROC-INST-CHART-TITLE', + 'values': [{ + 'key': 'Second Process', + 'y': 4, + 'keyAndValue': ['Second Process', '4'] + }, { + 'key': 'Simple process', + 'y': 30, + 'keyAndValue': ['Simple process', '30'] + }, { + 'key': 'Third Process', + 'y': 7, + 'keyAndValue': ['Third Process', '7'] + }] + }, { + 'id': 'id1585877659181', + 'type': 'table', + 'title': 'Process definition details', + 'titleKey': 'REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.DETAIL-TABLE', + 'columnNames': ['Process definition', 'Total', 'Active', 'Completed'], + 'columnNameKeys': ['REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.DETAIL-TABLE-PROCESS', + 'REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.DETAIL-TABLE-TOTAL', + 'REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.DETAIL-TABLE-ACTIVE', + 'REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.DETAIL-TABLE-COMPLETED'], + 'columnsCentered': [false, false, false, false], + 'rows': [ + ['Second Process', '4', '0', '4'], + ['Simple process', '30', '3', '27'], + ['Third Process', '7', '0', '7'] + ] + }] +}; + +export var chartTaskOverview = { + 'elements': [{ + 'id': 'id792351752194', + 'type': 'barChart', + 'title': 'title', + 'titleKey': 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.TASK-HISTOGRAM-TITLE', + 'values': [{ + 'key': 'series1', + 'values': [['2016-09-30T00:00:00.000+0000', 3], ['2016-10-04T00:00:00.000+0000', 1]] + }], + 'xAxisType': 'date_month', + 'yAxisType': 'count' + }, { + 'id': 'id792349721129', + 'type': 'masterDetailTable', + 'title': 'Detailed task statistics', + 'titleKey': 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.DETAILED-TASK-STATS-TITLE', + 'columnNames': ['Task', 'Count', 'Sum', 'Min duration', 'Max duration', 'Average duration', 'Stddev duration'], + 'columnNameKeys': [ + 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.DETAILED-TASK-STATS-TASK', + 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.COUNT', + 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.SUM', + 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.MIN-DURATION', + 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.MAX-DURATION', + 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.AVERAGE', + 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.STDDE'], + 'columnsCentered': [false, false, false, false], + 'rows': [ + ['fake 1 user task', '1', '2.0', '3.0', '4.0', '5.0', '6.0'], + ['fake 2 user task', '1', '2.0', '3.0', '4.0', '5.0', '6.0'] + ] + }, { + 'id': 'id10931125229538', + 'type': 'multiBarChart', + 'title': 'Task duration', + 'titleKey': 'REPORTING.DEFAULT-REPORTS.TASK-OVERVIEW.TASK-DURATIONS-TITLE', + 'values': [{ + 'key': 'averages', + 'values': [[1, 0], [2, 5], [3, 2]] + }, { + 'key': 'minima', + 'values': [[1, 0], [2, 0], [3, 0]] + }, { + 'key': 'maxima', + 'values': [[1, 0], [2, 29], [3, 29]] + }], + 'yAxisType': 'count' + }] +}; diff --git a/ng2-components/ng2-activiti-analytics/src/assets/analyticsParamsReportComponent.mock.ts b/ng2-components/ng2-activiti-analytics/src/assets/analyticsParamsReportComponent.mock.ts new file mode 100644 index 0000000000..07d44ade6a --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/assets/analyticsParamsReportComponent.mock.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 { ReportParameterDetailsModel } from '../models/report.model'; + +export var reportDefParamStatus = { + 'id': 2005, + 'name': 'Fake Task overview status', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters" :[{"id":"status","name":null,"nameKey":null,"type":"status","value":null,"dependsOn":null}]}' +}; + +export var reportDefParamNumber = { + 'id': 2005, + 'name': 'Fake Process instances overview', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters"' + + ' :[{"id":"slowProcessInstanceInteger","name":null,"nameKey":null,"type":"integer","value":10,"dependsOn":null}]}' +}; + +export var reportDefParamDuration = { + 'id': 2005, + 'name': 'Fake Task service level agreement', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters"' + + ' :[{"id":"duration","name":null,"nameKey":null,"type":"duration","value":null,"dependsOn":null}]}' +}; + +export var reportDefParamCheck = { + 'id': 2005, + 'name': 'Fake Task service level agreement', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters"' + + ' :[{"id":"typeFiltering","name":null,"nameKey":null,"type":"boolean","value":true,"dependsOn":null}]}' +}; + +export var reportDefParamDateRange = { + 'id': 2005, + 'name': 'Fake Process instances overview', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters" :[{"id":"dateRange","name":null,"nameKey":null,"type":"dateRange","value":null,"dependsOn":null}]}' +}; + +export var reportDefParamRangeInterval = { + 'id': 2006, + 'name': 'Fake Task overview RangeInterval', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters" :[{"id":"dateRangeInterval","name":null,"nameKey":null,"type":"dateInterval","value":null,"dependsOn":null}]}' +}; + +export var reportDefParamProcessDef = { + 'id': 2006, + 'name': 'Fake Task overview ProcessDefinition', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters" :[{"id":"processDefinitionId","name":null,"nameKey":null,"type":"processDefinition","value":null,"dependsOn":null}]}' +}; + +export var reportDefParamProcessDefOptionsNoApp = [ + { + 'id': 'FakeProcessTest 1:1:1', + 'name': 'Fake Process Test 1 Name ', + 'version': 1 + }, + { + 'id': 'FakeProcessTest 1:2:1', + 'name': 'Fake Process Test 1 Name ', + 'version': 2 + }, + { + 'id': 'FakeProcessTest 2:1:1', + 'name': 'Fake Process Test 2 Name ', + 'version': 1 + }, + { + 'id': 'FakeProcessTest 3:1:1', + 'name': 'Fake Process Test 3 Name ', + 'version': 1 + } +]; + +export var reportDefParamProcessDefOptions = { + 'size': 4, 'total': 4, 'start': 0, 'data': [ + { + 'id': 'FakeProcessTest 1:1:1', + 'name': 'Fake Process Test 1 Name ', + 'version': 1 + }, + { + 'id': 'FakeProcessTest 1:2:1', + 'name': 'Fake Process Test 1 Name ', + 'version': 2 + }, + { + 'id': 'FakeProcessTest 2:1:1', + 'name': 'Fake Process Test 2 Name ', + 'version': 1 + }, + { + 'id': 'FakeProcessTest 3:1:1', + 'name': 'Fake Process Test 3 Name ', + 'version': 1 + } + ] +}; + +export var reportDefParamProcessDefOptionsApp = { + 'size': 2, 'total': 2, 'start': 2, 'data': [ + { + 'id': 'FakeProcessTest 1:1:1', + 'name': 'Fake Process Test 1 Name ', + 'version': 1 + }, + { + 'id': 'FakeProcessTest 1:2:1', + 'name': 'Fake Process Test 1 Name ', + 'version': 2 + } + ] +}; + +export var reportDefParamTask = { + 'id': 2006, + 'name': 'Fake Task service level agreement', + 'created': '2016-10-05T15:39:40.222+0000', + 'definition': '{ "parameters" :[{"id":"taskName","name":null,"nameKey":null,"type":"task","value":null,"dependsOn":"processDefinitionId"}]}' +}; + +export var reportDefParamTaskOptions = ['Fake task name 1', 'Fake task name 2']; + +export var fieldProcessDef = new ReportParameterDetailsModel( + { + id: 'processDefinitionId', + type: 'processDefinition', + value: 'fake-process-name:1:15027' + } +); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.html b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.html new file mode 100644 index 0000000000..e9c271f63f --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.html @@ -0,0 +1,9 @@ +

Process Heat map

+
+
+ +
+ +
+
No metric found
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.spec.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.spec.ts new file mode 100644 index 0000000000..3be8225c59 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.spec.ts @@ -0,0 +1,128 @@ +/*! + * @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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { CoreModule } from 'ng2-alfresco-core'; +import { DiagramsModule } from 'ng2-activiti-diagrams'; +import { AnalyticsReportHeatMapComponent } from '../components/analytics-report-heat-map.component'; +import { WIDGET_DIRECTIVES } from '../components/widgets/index'; +import { AnalyticsService } from '../services/analytics.service'; +import { DebugElement } from '@angular/core'; + +declare let jasmine: any; + +describe('Test ng2-activiti-analytics-report-heat-map', () => { + + let componentHandler: any; + let component: any; + let fixture: ComponentFixture; + let debug: DebugElement; + let element: HTMLElement; + + let totalCountPerc = {'sid-fake-id': 0, 'fake-start-event': 100}; + let totalTimePerc = {'sid-fake-id': 10, 'fake-start-event': 30}; + let avgTimePercentages = {'sid-fake-id': 5, 'fake-start-event': 50}; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule, + DiagramsModule + ], + declarations: [ + AnalyticsReportHeatMapComponent, + ...WIDGET_DIRECTIVES + ], + providers: [ + AnalyticsService + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalyticsReportHeatMapComponent); + component = fixture.componentInstance; + debug = fixture.debugElement; + element = fixture.nativeElement; + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + + component.report = { + totalCountsPercentages: totalCountPerc, + totalTimePercentages: totalTimePerc, + avgTimePercentages: avgTimePercentages + }; + }); + + describe('Rendering tests: Heat Map', () => { + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should render the dropdown with the metric options', async(() => { + component.report = {totalCountsPercentages: {'sid-fake-id': 10, 'fake-start-event': 30}}; + + component.onSuccess.subscribe(() => { + fixture.whenStable().then(() => { + let dropDown: any = element.querySelector('#select-metrics'); + expect(dropDown).toBeDefined(); + expect(dropDown.length).toEqual(3); + expect(dropDown[0].innerHTML).toEqual('Number of times a step is executed'); + expect(dropDown[1].innerHTML).toEqual('Total time spent in a process step'); + expect(dropDown[2].innerHTML).toEqual('Average time spent in a process step'); + }); + }); + fixture.detectChanges(); + })); + + it('should return false when no metrics are defined in the report', async(() => { + component.report = {}; + expect(component.hasMetric()).toBeFalsy(); + })); + + it('should return true when the metrics are defined in the report', async(() => { + expect(component.hasMetric()).toBeTruthy(); + })); + + it('should change the currentmetric width totalCount', async(() => { + let field = {value: 'totalCount'}; + component.onMetricChanges(field); + expect(component.currentMetric).toEqual(totalCountPerc); + })); + + it('should change the currentmetric width totalTime', async(() => { + let field = {value: 'totalTime'}; + component.onMetricChanges(field); + expect(component.currentMetric).toEqual(totalTimePerc); + })); + + it('should change the currentmetric width avgTime', async(() => { + let field = {value: 'avgTime'}; + component.onMetricChanges(field); + expect(component.currentMetric).toEqual(avgTimePercentages); + })); + + }); + +}); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.ts new file mode 100644 index 0000000000..1087157e24 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-heat-map.component.ts @@ -0,0 +1,89 @@ +/*! + * @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 { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { AnalyticsService } from '../services/analytics.service'; +import { FormGroup, FormBuilder, FormControl } from '@angular/forms'; + +@Component({ + moduleId: module.id, + selector: 'analytics-report-heat-map', + templateUrl: './analytics-report-heat-map.component.html' +}) +export class AnalyticsReportHeatMapComponent implements OnInit { + + @Input() + report: any; + + @Output() + onSuccess = new EventEmitter(); + + @Output() + onError = new EventEmitter(); + + field: any = {}; + + metricForm: FormGroup; + currentMetric: string; + + constructor(private translate: AlfrescoTranslationService, + private analyticsService: AnalyticsService, + private formBuilder: FormBuilder) { + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src'); + } + } + + ngOnInit() { + this.initForm(); + this.field.id = 'metrics'; + this.field.value = 'totalCount'; + + this.analyticsService.getMetricValues().subscribe( + (opts: any[]) => { + this.field.options = opts; + this.onSuccess.emit(opts); + } + ); + } + + onMetricChanges(field: any) { + if (field.value === 'totalCount') { + this.currentMetric = this.report.totalCountsPercentages; + } else if (field.value === 'totalTime') { + this.currentMetric = this.report.totalTimePercentages; + } else if (field.value === 'avgTime') { + this.currentMetric = this.report.avgTimePercentages; + } + } + + initForm() { + this.metricForm = this.formBuilder.group({ + metricGroup: new FormGroup({ + metric: new FormControl() + }) + }); + } + + hasMetric() { + return (this.report.totalCountsPercentages || + this.report.totalTimePercentages || + this.report.avgTimePercentages) ? true : false; + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.css b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.css new file mode 100644 index 0000000000..073fb82bbd --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.css @@ -0,0 +1,19 @@ +.mdl-list__item { + cursor: pointer; +} + +.activiti-filters__entry { + cursor: pointer; +} + +.activiti-filters__entry-icon { + margin-right: 12px !important; +} + +.activiti-filters__entry.active { + color: rgb(68,138,255); +} + +.activiti-filters__entry.active .activiti-filters__entry-icon { + color: rgb(68,138,255); +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.html b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.html new file mode 100644 index 0000000000..38a6af1268 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.spec.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.spec.ts new file mode 100644 index 0000000000..8709b8157c --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.spec.ts @@ -0,0 +1,165 @@ +/*! + * @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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { CoreModule } from 'ng2-alfresco-core'; +import { AnalyticsReportListComponent } from '../components/analytics-report-list.component'; +import { AnalyticsService } from '../services/analytics.service'; +import { DebugElement } from '@angular/core'; + +declare let jasmine: any; + +describe('Test ng2-activiti-analytics Report list', () => { + + let reportList = [ + {'id': 2002, 'name': 'Fake Test Process definition heat map'}, + {'id': 2003, 'name': 'Fake Test Process definition overview'}, + {'id': 2004, 'name': 'Fake Test Process instances overview'}, + {'id': 2005, 'name': 'Fake Test Task overview'}, + {'id': 2006, 'name': 'Fake Test Task service level agreement'} + ]; + + let reportSelected = {'id': 2003, 'name': 'Fake Test Process definition overview'}; + + let component: any; + let fixture: ComponentFixture; + let debug: DebugElement; + let element: HTMLElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule + ], + declarations: [ + AnalyticsReportListComponent + ], + providers: [ + AnalyticsService + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalyticsReportListComponent); + component = fixture.componentInstance; + debug = fixture.debugElement; + element = fixture.nativeElement; + }); + + describe('Rendering tests', () => { + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Report return true with undefined reports', () => { + expect(component.isReportsEmpty()).toBeTruthy(); + + }); + + it('Report return true with an empty reports', () => { + component.reports = []; + expect(component.isReportsEmpty()).toBeTruthy(); + }); + + it('should return the default reports when the report list is empty', (done) => { + fixture.detectChanges(); + + component.onSuccess.subscribe(() => { + fixture.detectChanges(); + expect(element.querySelector('#report-list-0 > i').innerHTML).toBe('assignment'); + expect(element.querySelector('#report-list-0 > span').innerHTML).toBe('Fake Test Process definition heat map'); + expect(element.querySelector('#report-list-1 > span').innerHTML).toBe('Fake Test Process definition overview'); + expect(element.querySelector('#report-list-2 > span').innerHTML).toBe('Fake Test Process instances overview'); + expect(element.querySelector('#report-list-3 > span').innerHTML).toBe('Fake Test Task overview'); + expect(element.querySelector('#report-list-4 > span').innerHTML).toBe('Fake Test Task service level agreement'); + expect(component.isReportsEmpty()).toBeFalsy(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: [] + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: [] + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: reportList + }); + }); + + it('Report render the report list relative to a single app', (done) => { + fixture.detectChanges(); + + component.onSuccess.subscribe(() => { + fixture.detectChanges(); + expect(element.querySelector('#report-list-0 > i').innerHTML).toBe('assignment'); + expect(element.querySelector('#report-list-0 > span').innerHTML).toBe('Fake Test Process definition heat map'); + expect(element.querySelector('#report-list-1 > span').innerHTML).toBe('Fake Test Process definition overview'); + expect(element.querySelector('#report-list-2 > span').innerHTML).toBe('Fake Test Process instances overview'); + expect(element.querySelector('#report-list-3 > span').innerHTML).toBe('Fake Test Task overview'); + expect(element.querySelector('#report-list-4 > span').innerHTML).toBe('Fake Test Task service level agreement'); + expect(component.isReportsEmpty()).toBeFalsy(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: reportList + }); + }); + + it('Report emit an error with a empty response', (done) => { + fixture.detectChanges(); + + component.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 404, + contentType: 'json', + responseText: [] + }); + }); + + it('Should return the current report when one report is selected', () => { + component.reportClick.subscribe(() => { + expect(component.currentReport).toEqual(reportSelected); + }); + + component.selectReport(reportSelected); + }); + + }); + +}); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.ts new file mode 100644 index 0000000000..979d8ad0b1 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.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, EventEmitter, OnInit, Output } from '@angular/core'; +import { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { AnalyticsService } from '../services/analytics.service'; +import { ReportParametersModel } from '../models/report.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +@Component({ + moduleId: module.id, + selector: 'analytics-report-list', + templateUrl: './analytics-report-list.component.html', + styleUrls: ['./analytics-report-list.component.css'] +}) +export class AnalyticsReportListComponent implements OnInit { + + @Output() + reportClick: EventEmitter = new EventEmitter(); + + @Output() + onSuccess = new EventEmitter(); + + @Output() + onError = new EventEmitter(); + + private reportObserver: Observer; + report$: Observable; + + currentReport: any; + + reports: ReportParametersModel[] = []; + + constructor(private auth: AlfrescoAuthenticationService, + private analyticsService: AnalyticsService) { + + this.report$ = new Observable(observer => this.reportObserver = observer).share(); + } + + ngOnInit() { + this.report$.subscribe((report: ReportParametersModel) => { + this.reports.push(report); + }); + + this.getReportListByAppId(); + } + + /** + * Get the report list by app id + */ + getReportListByAppId() { + this.analyticsService.getReportList().subscribe( + (res: ReportParametersModel[]) => { + if (res && res.length === 0) { + this.createDefaultReports(); + } else { + res.forEach((report) => { + this.reportObserver.next(report); + }); + this.onSuccess.emit(res); + } + }, + (err: any) => { + this.onError.emit(err); + console.log(err); + } + ); + } + + /** + * Create the default reports and return the report list + */ + createDefaultReports() { + this.analyticsService.createDefaultReports().subscribe( + () => { + this.analyticsService.getReportList().subscribe( + (response: ReportParametersModel[]) => { + response.forEach((report) => { + this.reportObserver.next(report); + }); + this.onSuccess.emit(response); + } + ); + } + ); + } + + /** + * Check if the report list is empty + * @returns {boolean|ReportParametersModel[]} + */ + isReportsEmpty(): boolean { + return this.reports === undefined || (this.reports && this.reports.length === 0); + } + + /** + * Select the current report + * @param report + */ + public selectReport(report: any) { + this.currentReport = report; + this.reportClick.emit(report); + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.css b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.css new file mode 100644 index 0000000000..ae995ca854 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.css @@ -0,0 +1,23 @@ +.dropdown-widget { + width: 100%; +} + +.dropdown-widget__select { + width: 100%; +} + +.dropdown-widget__invalid .dropdown-widget__select { + border-color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label { + color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label:after { + background-color: #d50000; +} + +.dropdown-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.html b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.html new file mode 100644 index 0000000000..3fe322d9f7 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.html @@ -0,0 +1,58 @@ +
+
+
+

{{reportParameters.name}}

+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ {{'ANALYTICS.MESSAGES.UNKNOWN-WIDGET-TYPE' | translate}}: {{field.type}} +
+
+
+
+

ReportForm valid : {{ reportForm.valid }}

+

ReportForm status : {{ reportForm.errors | json }}

+

ReportForm FormGroup valid : {{reportForm && reportForm.controls.dateRange.valid | json }}

+
+
+
+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.spec.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.spec.ts new file mode 100644 index 0000000000..bb435e61cc --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.spec.ts @@ -0,0 +1,397 @@ +/*! + * @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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { + CoreModule +} from 'ng2-alfresco-core'; + +import { AnalyticsReportParametersComponent } from '../components/analytics-report-parameters.component'; +import { WIDGET_DIRECTIVES } from '../components/widgets/index'; + +import { AnalyticsService } from '../services/analytics.service'; +import { ReportParametersModel } from '../models/report.model'; +import * as moment from 'moment'; +import { DebugElement, SimpleChange } from '@angular/core'; +import * as analyticParamsMock from '../assets/analyticsParamsReportComponent.mock'; + +declare let jasmine: any; +declare let mdDateTimePicker: any; + +describe('Test ng2-analytics-report-parameters Report Parameters ', () => { + + let component: any; + let fixture: ComponentFixture; + let debug: DebugElement; + let element: HTMLElement; + + let componentHandler: any; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule + ], + declarations: [ + AnalyticsReportParametersComponent, + ...WIDGET_DIRECTIVES + ], + providers: [ + AnalyticsService + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalyticsReportParametersComponent); + component = fixture.componentInstance; + debug = fixture.debugElement; + element = fixture.nativeElement; + fixture.detectChanges(); + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + }); + + describe('Rendering tests', () => { + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should initialize the Report form with a Form Group ', () => { + expect(component.reportForm.get('dateRange')).toBeDefined(); + expect(component.reportForm.get('dateRange').get('startDate')).toBeDefined(); + expect(component.reportForm.get('dateRange').get('endDate')).toBeDefined(); + }); + + it('Should render a dropdown with all the status when the definition parameter type is \'status\' ', (done) => { + component.onSuccessReportParams.subscribe(() => { + fixture.detectChanges(); + let dropDown: any = element.querySelector('#select-status'); + expect(element.querySelector('h1').innerHTML).toEqual('Fake Task overview status'); + expect(dropDown).toBeDefined(); + expect(dropDown.length).toEqual(4); + expect(dropDown[0].innerHTML).toEqual('Choose One'); + expect(dropDown[1].innerHTML).toEqual('All'); + expect(dropDown[2].innerHTML).toEqual('Active'); + expect(dropDown[3].innerHTML).toEqual('Complete'); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamStatus + }); + }); + + it('Should render a number with the default value when the definition parameter type is \'integer\' ', (done) => { + component.onSuccessReportParams.subscribe(() => { + fixture.detectChanges(); + let numberElement: any = element.querySelector('#slowProcessInstanceInteger'); + expect(numberElement.value).toEqual('10'); + + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamNumber + }); + }); + + it('Should render a duration component when the definition parameter type is \'duration\' ', (done) => { + component.onSuccessReportParams.subscribe(() => { + fixture.detectChanges(); + let numberElement: any = element.querySelector('#duration'); + expect(numberElement.value).toEqual('0'); + + let dropDown: any = element.querySelector('#select-duration'); + expect(dropDown).toBeDefined(); + expect(dropDown.length).toEqual(4); + expect(dropDown[0].innerHTML).toEqual('Seconds'); + expect(dropDown[1].innerHTML).toEqual('Minutes'); + expect(dropDown[2].innerHTML).toEqual('Hours'); + expect(dropDown[3].innerHTML).toEqual('Days'); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamDuration + }); + }); + + it('Should save an Params object when the submit is performed', () => { + component.onSuccess.subscribe((res) => { + expect(res.dateRange.startDate).toEqual('2016-09-01T00:00:00.000Z'); + expect(res.dateRange.endDate).toEqual('2016-10-05T00:00:00.000Z'); + expect(res.status).toEqual('All'); + expect(res.processDefinitionId).toEqual('FakeProcess:1:22'); + expect(res.taskName).toEqual('FakeTaskName'); + expect(res.duration).toEqual(22); + expect(res.dateRangeInterval).toEqual(120); + expect(res.slowProcessInstanceInteger).toEqual(2); + expect(res.typeFiltering).toEqual(true); + }); + + let values: any = { + dateRange: { + startDate: '2016-09-01', endDate: '2016-10-05' + }, + statusGroup: { + status: 'All' + }, + processDefGroup: { + processDefinitionId: 'FakeProcess:1:22' + }, + taskGroup: { + taskName: 'FakeTaskName' + }, + durationGroup: { + duration: 22 + }, + dateIntervalGroup: { + dateRangeInterval: 120 + }, + processInstanceGroup: { + slowProcessInstanceInteger: 2 + }, + typeFilteringGroup: { + typeFiltering: true + } + }; + component.submit(values); + }); + + it('Should render a checkbox with the value true when the definition parameter type is \'boolean\' ', (done) => { + component.onSuccessReportParams.subscribe(() => { + fixture.detectChanges(); + let checkElement: any = element.querySelector('#typeFiltering'); + expect(checkElement.checked).toBeTruthy(); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamCheck + }); + }); + + it('Should render a date range components when the definition parameter type is \'dateRange\' ', (done) => { + component.onSuccessReportParams.subscribe(() => { + fixture.detectChanges(); + let today = moment().format('YYYY-MM-DD'); + + const startDate: any = element.querySelector('#startDateInput'); + const endDate: any = element.querySelector('#endDateInput'); + + expect(startDate.value).toEqual(today); + expect(endDate.value).toEqual(today); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamDateRange + }); + }); + + it('Should render a dropdown with all the RangeInterval when the definition parameter type is \'dateRangeInterval\' ', (done) => { + component.onSuccessReportParams.subscribe(() => { + fixture.detectChanges(); + let dropDown: any = element.querySelector('#select-dateRangeInterval'); + expect(dropDown).toBeDefined(); + expect(dropDown.length).toEqual(5); + expect(dropDown[0].innerHTML).toEqual('By hour'); + expect(dropDown[1].innerHTML).toEqual('By day'); + expect(dropDown[2].innerHTML).toEqual('By week'); + expect(dropDown[3].innerHTML).toEqual('By month'); + expect(dropDown[4].innerHTML).toEqual('By year'); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamRangeInterval + }); + }); + + it('Should render a dropdown with all the process definition when the definition parameter type is \'processDefinition\' and the' + + ' reportId change', (done) => { + component.onSuccessParamOpt.subscribe(() => { + fixture.detectChanges(); + let dropDown: any = element.querySelector('#select-processDefinitionId'); + expect(dropDown).toBeDefined(); + expect(dropDown.length).toEqual(5); + expect(dropDown[0].innerHTML).toEqual('Choose One'); + expect(dropDown[1].innerHTML).toEqual('Fake Process Test 1 Name (v 1) '); + expect(dropDown[2].innerHTML).toEqual('Fake Process Test 1 Name (v 2) '); + expect(dropDown[3].innerHTML).toEqual('Fake Process Test 2 Name (v 1) '); + expect(dropDown[4].innerHTML).toEqual('Fake Process Test 3 Name (v 1) '); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.first().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamProcessDef + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamProcessDefOptionsNoApp + }); + }); + + it('Should render a dropdown with all the process definition when the definition parameter type is \'processDefinition\' and the' + + ' appId change', (done) => { + component.onSuccessParamOpt.subscribe(() => { + fixture.detectChanges(); + let dropDown: any = element.querySelector('#select-processDefinitionId'); + expect(dropDown).toBeDefined(); + expect(dropDown.length).toEqual(3); + expect(dropDown[0].innerHTML).toEqual('Choose One'); + expect(dropDown[1].innerHTML).toEqual('Fake Process Test 1 Name (v 1) '); + expect(dropDown[2].innerHTML).toEqual('Fake Process Test 1 Name (v 2) '); + done(); + }); + + let appId = 1; + component.appId = appId; + let change = new SimpleChange(null, appId); + component.ngOnChanges({ 'appId': change }); + + jasmine.Ajax.requests.first().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamProcessDef + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamProcessDefOptionsApp + }); + }); + + it('Should load the task list when a process definition is selected', () => { + component.onSuccessReportParams.subscribe((res) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].id).toEqual('Fake task name 1'); + expect(res[0].name).toEqual('Fake task name 1'); + expect(res[1].id).toEqual('Fake task name 2'); + expect(res[1].name).toEqual('Fake task name 2'); + }); + + component.reportId = 100; + component.reportParameters = new ReportParametersModel(analyticParamsMock.reportDefParamTask); + component.onProcessDefinitionChanges(analyticParamsMock.fieldProcessDef); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamTaskOptions + }); + }); + + it('Should emit an error with a 404 response when the options response is not found', (done) => { + component.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.first().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticParamsMock.reportDefParamProcessDef + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 404, + contentType: 'json', + responseText: [] + }); + }); + + it('Should emit an error with a 404 response when the report parameters response is not found', (done) => { + component.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + let reportId = 1; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 404, + contentType: 'json', + responseText: [] + }); + }); + }); + + it('Should convert a string in number', () => { + let numberConvert = component.convertNumber('2'); + expect(numberConvert).toEqual(2); + }); +}); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.ts new file mode 100644 index 0000000000..15a4377d2f --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.ts @@ -0,0 +1,213 @@ +/*! + * @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, EventEmitter, OnInit, OnChanges, Input, Output, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { AnalyticsService } from '../services/analytics.service'; +import { ReportParametersModel, ReportQuery, ParameterValueModel, ReportParameterDetailsModel } from '../models/report.model'; +import { FormGroup, FormBuilder, FormControl } from '@angular/forms'; +import * as moment from 'moment'; + +@Component({ + moduleId: module.id, + selector: 'analytics-report-parameters', + templateUrl: './analytics-report-parameters.component.html', + styleUrls: ['./analytics-report-parameters.component.css'] +}) +export class AnalyticsReportParametersComponent implements OnInit, OnChanges { + + public static FORMAT_DATE_ACTIVITI: string = 'YYYY-MM-DD'; + + @Input() + appId: string; + + @Input() + reportId: string; + + @Input() + debug: boolean = false; + + @Output() + onSuccess = new EventEmitter(); + + @Output() + onError = new EventEmitter(); + + @Output() + onFormValueChanged = new EventEmitter(); + + onDropdownChanged = new EventEmitter(); + + onSuccessReportParams = new EventEmitter(); + + onSuccessParamOpt = new EventEmitter(); + + reportParameters: ReportParametersModel; + + reportForm: FormGroup; + + private dropDownSub; + private reportParamsSub; + private paramOpts; + + constructor(private translate: AlfrescoTranslationService, + private analyticsService: AnalyticsService, + private formBuilder: FormBuilder ) { + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src'); + } + } + + ngOnInit() { + this.initForm(); + + this.dropDownSub = this.onDropdownChanged.subscribe((field) => { + let paramDependOn: ReportParameterDetailsModel = this.reportParameters.definition.parameters.find(p => p.dependsOn === field.id); + if (paramDependOn) { + this.retrieveParameterOptions(this.reportParameters.definition.parameters, this.appId, this.reportId, field.value); + } + }); + + this.paramOpts = this.onSuccessReportParams.subscribe((report: ReportParametersModel) => { + if (report.hasParameters()) { + this.retrieveParameterOptions(report.definition.parameters, this.appId); + } + }); + + this.reportForm.valueChanges.subscribe(data => this.onValueChanged(data)); + } + + ngOnChanges(changes: SimpleChanges) { + let reportId = changes['reportId']; + if (reportId && reportId.currentValue) { + this.getReportParams(reportId.currentValue); + } + + let appId = changes['appId']; + if (appId && (appId.currentValue || appId.currentValue === null)) { + this.getReportParams(this.reportId); + } + } + + initForm() { + this.reportForm = this.formBuilder.group({ + dateRange: new FormGroup({}), + statusGroup: new FormGroup({ + status: new FormControl() + }), + processInstanceGroup: new FormGroup({ + slowProcessInstanceInteger: new FormControl() + }), + taskGroup: new FormGroup({ + taskName: new FormControl() + }), + typeFilteringGroup: new FormGroup({ + typeFiltering: new FormControl() + }), + dateIntervalGroup: new FormGroup({ + dateRangeInterval: new FormControl() + }), + durationGroup: new FormGroup({ + duration: new FormControl() + }), + processDefGroup: new FormGroup({ + processDefinitionId: new FormControl() + }) + }); + } + + public getReportParams(reportId: string) { + this.reportParamsSub = this.analyticsService.getReportParams(reportId).subscribe( + (res: ReportParametersModel) => { + this.reportParameters = res; + if (this.reportParameters.hasParameters()) { + this.onSuccessReportParams.emit(res); + } else { + this.onSuccess.emit(); + } + }, + (err: any) => { + console.log(err); + this.onError.emit(err); + } + ); + } + + private retrieveParameterOptions(parameters: ReportParameterDetailsModel[], appId: string, reportId?: string, processDefinitionId?: string) { + parameters.forEach((param) => { + this.analyticsService.getParamValuesByType(param.type, appId, reportId, processDefinitionId).subscribe( + (opts: ParameterValueModel[]) => { + param.options = opts; + this.onSuccessParamOpt.emit(opts); + }, + (err: any) => { + console.log(err); + this.onError.emit(err); + } + ); + }); + } + + onProcessDefinitionChanges(field: any) { + if (field.value) { + this.onDropdownChanged.emit(field); + } + } + + public submit(values: any) { + let reportParamQuery = this.convertFormValuesToReportParamQuery(values); + this.onSuccess.emit(reportParamQuery); + } + + onValueChanged(values: any) { + this.onFormValueChanged.emit(values); + if (this.reportForm && this.reportForm.valid) { + this.submit(values); + } + } + + public convertMomentDate(date: string) { + return moment(date, AnalyticsReportParametersComponent.FORMAT_DATE_ACTIVITI, true) + .format(AnalyticsReportParametersComponent.FORMAT_DATE_ACTIVITI) + 'T00:00:00.000Z'; + } + + public convertNumber(value: string): number { + return value != null ? parseInt(value, 10) : 0; + } + + convertFormValuesToReportParamQuery(values: any): ReportQuery { + let reportParamQuery: ReportQuery = new ReportQuery(); + reportParamQuery.dateRange.startDate = this.convertMomentDate(values.dateRange.startDate); + reportParamQuery.dateRange.endDate = this.convertMomentDate(values.dateRange.endDate); + reportParamQuery.status = values.statusGroup.status; + reportParamQuery.processDefinitionId = values.processDefGroup.processDefinitionId; + reportParamQuery.taskName = values.taskGroup.taskName; + reportParamQuery.duration = values.durationGroup.duration; + reportParamQuery.dateRangeInterval = values.dateIntervalGroup.dateRangeInterval; + reportParamQuery.slowProcessInstanceInteger = this.convertNumber(values.processInstanceGroup.slowProcessInstanceInteger); + reportParamQuery.typeFiltering = values.typeFilteringGroup.typeFiltering; + return reportParamQuery; + } + + ngOnDestroy() { + this.dropDownSub.unsubscribe(); + this.paramOpts.unsubscribe(); + if (this.reportParamsSub) { + this.reportParamsSub.unsubscribe(); + } + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.css b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.css new file mode 100644 index 0000000000..096757598a --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.css @@ -0,0 +1,25 @@ +.chart {display: block; width: 100%;} + +.dropdown-widget { + width: 100%; +} + +.dropdown-widget__select { + width: 100%; +} + +.dropdown-widget__invalid .dropdown-widget__select { + border-color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label { + color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label:after { + background-color: #d50000; +} + +.dropdown-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.html b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.html new file mode 100644 index 0000000000..3f0e5c2e8c --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.html @@ -0,0 +1,84 @@ +
+ + +
+
+

{{report.title}}

+
+
+
+
{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}
+ +
+
+
+
{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}
+
+ + + + + + + +
{{label | translate}}
{{row | translate }}
+
+
+
+
{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}
+
+ + + + + + + +
{{label | translate}}
{{row | translate }}
+
+
+
+
+
{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}
+ +
+
+
+
+
{{'ANALYTICS.MESSAGES.NO-DATA-FOUND' | translate}}
+
+ + +
+
+
+
+ +
+
+ {{'ANALYTICS.MESSAGES.UNKNOWN-WIDGET-TYPE' | translate}}: {{report.type}} +
+
+
+
+


+
{{'ANALYTICS.MESSAGES.FILL-PARAMETER' | translate}}
+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.spec.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.spec.ts new file mode 100644 index 0000000000..ea77c223f8 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.spec.ts @@ -0,0 +1,226 @@ +/*! + * @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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { + CoreModule +} from 'ng2-alfresco-core'; +import { DiagramsModule } from 'ng2-activiti-diagrams'; + +import { AnalyticsReportListComponent } from '../components/analytics-report-list.component'; +import { AnalyticsComponent } from '../components/analytics.component'; +import { AnalyticsReportParametersComponent } from '../components/analytics-report-parameters.component'; +import { AnalyticsReportHeatMapComponent } from '../components/analytics-report-heat-map.component'; +import { WIDGET_DIRECTIVES } from '../components/widgets/index'; +import { CHART_DIRECTIVES } from 'ng2-charts/ng2-charts'; +import { Chart } from '../models/chart.model'; +import { AnalyticsService } from '../services/analytics.service'; +import { ReportQuery } from '../models/report.model'; +import { DebugElement, SimpleChange } from '@angular/core'; +import * as analyticMock from '../assets/analyticsComponent.mock'; + +export const ANALYTICS_DIRECTIVES: any[] = [ + AnalyticsComponent, + AnalyticsReportParametersComponent, + AnalyticsReportListComponent, + AnalyticsReportHeatMapComponent, + WIDGET_DIRECTIVES +]; +export const ANALYTICS_PROVIDERS: any[] = [ + AnalyticsService +]; + +declare let jasmine: any; +declare let mdDateTimePicker: any; + +describe('Test ng2-activiti-analytics Report ', () => { + + let component: any; + let fixture: ComponentFixture; + let debug: DebugElement; + let element: HTMLElement; + + let componentHandler: any; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule, + DiagramsModule + ], + declarations: [ + ...ANALYTICS_DIRECTIVES, + ...CHART_DIRECTIVES + ], + providers: [ + ...ANALYTICS_PROVIDERS + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalyticsComponent); + component = fixture.componentInstance; + debug = fixture.debugElement; + element = fixture.nativeElement; + fixture.detectChanges(); + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + }); + + describe('Rendering tests', () => { + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Process definition overview report ', (done) => { + component.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(3); + + expect(res[0]).toBeDefined(); + expect(res[0].type).toEqual('table'); + expect(res[0].datasets).toBeDefined(); + expect(res[0].datasets.length).toEqual(4); + expect(res[0].datasets[0][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-TOTAL-PROCESS-DEFINITIONS'); + expect(res[0].datasets[0][1]).toEqual('9'); + expect(res[0].datasets[1][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-TOTAL-PROCESS-INSTANCES'); + expect(res[0].datasets[1][1]).toEqual('41'); + expect(res[0].datasets[2][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-ACTIVE-PROCESS-INSTANCES'); + expect(res[0].datasets[2][1]).toEqual('3'); + expect(res[0].datasets[3][0]).toEqual('__KEY_REPORTING.DEFAULT-REPORTS.PROCESS-DEFINITION-OVERVIEW.GENERAL-TABLE-COMPLETED-PROCESS-INSTANCES'); + expect(res[0].datasets[3][1]).toEqual('38'); + + expect(res[1]).toBeDefined(); + expect(res[1].type).toEqual('pie'); + + expect(res[2]).toBeDefined(); + expect(res[2].type).toEqual('table'); + + done(); + }); + + let reportParamQuery = new ReportQuery({status: 'All'}); + component.appId = 1; + component.reportId = 1001; + component.showReport(reportParamQuery); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticMock.chartProcessDefOverview + }); + }); + + it('Should render the Task overview report ', (done) => { + component.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(3); + + expect(res[0]).toBeDefined(); + expect(res[0].type).toEqual('bar'); + expect(res[0].labels).toBeDefined(); + expect(res[0].labels.length).toEqual(2); + expect(res[0].labels[0]).toEqual('2016-09-30T00:00:00.000+0000'); + expect(res[0].labels[1]).toEqual('2016-10-04T00:00:00.000+0000'); + expect(res[0].datasets[0].label).toEqual('series1'); + expect(res[0].datasets[0].data[0]).toEqual(3); + expect(res[0].datasets[0].data[1]).toEqual(1); + + expect(res[1]).toBeDefined(); + expect(res[1].type).toEqual('table'); + expect(res[1].datasets).toBeDefined(); + expect(res[1].datasets.length).toEqual(2); + expect(res[1].datasets[0][0]).toEqual('fake 1 user task'); + expect(res[1].datasets[0][1]).toEqual('1'); + expect(res[1].datasets[0][2]).toEqual('2.0'); + expect(res[1].datasets[0][3]).toEqual('3.0'); + expect(res[1].datasets[0][4]).toEqual('4.0'); + expect(res[1].datasets[0][5]).toEqual('5.0'); + expect(res[1].datasets[0][6]).toEqual('6.0'); + expect(res[1].datasets[1][0]).toEqual('fake 2 user task'); + expect(res[1].datasets[1][1]).toEqual('1'); + expect(res[1].datasets[1][2]).toEqual('2.0'); + expect(res[1].datasets[1][3]).toEqual('3.0'); + expect(res[1].datasets[1][4]).toEqual('4.0'); + expect(res[1].datasets[1][5]).toEqual('5.0'); + expect(res[1].datasets[1][6]).toEqual('6.0'); + + expect(res[2]).toBeDefined(); + expect(res[2].type).toEqual('multiBar'); + expect(res[2].labels).toBeDefined(); + expect(res[2].labels.length).toEqual(3); + expect(res[2].labels[0]).toEqual(1); + expect(res[2].labels[1]).toEqual(2); + expect(res[2].labels[2]).toEqual(3); + expect(res[2].datasets[0].label).toEqual('averages'); + expect(res[2].datasets[0].data[0]).toEqual(0); + expect(res[2].datasets[0].data[1]).toEqual(5); + expect(res[2].datasets[0].data[2]).toEqual(2); + expect(res[2].datasets[1].label).toEqual('minima'); + expect(res[2].datasets[1].data[0]).toEqual(0); + expect(res[2].datasets[1].data[1]).toEqual(0); + expect(res[2].datasets[1].data[2]).toEqual(0); + expect(res[2].datasets[2].label).toEqual('maxima'); + expect(res[2].datasets[2].data[0]).toEqual(0); + expect(res[2].datasets[2].data[1]).toEqual(29); + expect(res[2].datasets[2].data[2]).toEqual(29); + + done(); + }); + + let reportParamQuery = new ReportQuery({status: 'All'}); + component.showReport(reportParamQuery); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: analyticMock.chartTaskOverview + }); + }); + + it('Should reset the reports when the onChanged is call', () => { + let reportId = 1; + component.reports = [ new Chart({id: 'fake', type: 'fake-type'})]; + let change = new SimpleChange(null, reportId); + component.ngOnChanges({ 'reportId': change }); + expect(component.reports).toBeUndefined(); + }); + + it('Should emit onError event with a 404 response ', (done) => { + component.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + let reportParamQuery = new ReportQuery({status: 'All'}); + component.showReport(reportParamQuery); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 404, + contentType: 'json', + responseText: [] + }); + }); + }); +}); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.ts new file mode 100644 index 0000000000..d3b159e321 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.ts @@ -0,0 +1,110 @@ +/*! + * @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, EventEmitter, OnChanges, Input, Output, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { AnalyticsService } from '../services/analytics.service'; +import { ReportQuery } from '../models/report.model'; +import { Chart } from '../models/chart.model'; + +@Component({ + moduleId: module.id, + selector: 'activiti-analytics', + templateUrl: './analytics.component.html', + styleUrls: ['./analytics.component.css'] +}) +export class AnalyticsComponent implements OnChanges { + + @Input() + appId: string; + + @Input() + reportId: number; + + @Input() + debug: boolean = false; + + @Output() + onSuccess = new EventEmitter(); + + @Output() + onError = new EventEmitter(); + + reportParamQuery = new ReportQuery(); + + reports: any[]; + + public barChartOptions: any = { + responsive: true, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true, + stepSize: 1 + } + }], + xAxes: [{ + ticks: { + }, + stacked: true + }] + } + }; + + constructor(private translate: AlfrescoTranslationService, + private analyticsService: AnalyticsService) { + console.log('AnalyticsComponent'); + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src'); + } + } + + ngOnChanges(changes: SimpleChanges) { + this.reset(); + } + + public showReport($event) { + this.reportParamQuery = $event; + this.analyticsService.getReportsByParams(this.reportId, this.reportParamQuery).subscribe( + (res: Chart[]) => { + this.reports = res; + this.onSuccess.emit(res); + }, + (err: any) => { + this.onError.emit(err); + console.log(err); + } + ); + } + + public reset() { + if (this.reports) { + this.reports = undefined; + } + } + + public refresh(report): void { + /** + * (My guess), for Angular to recognize the change in the dataset + * it has to change the dataset variable directly, + * so one way around it, is to clone the data, change it and then + * assign it; + */ + let clone = JSON.parse(JSON.stringify(report)); + report.datasets = clone.datasets; + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.html new file mode 100644 index 0000000000..4d02c9a7da --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.ts new file mode 100644 index 0000000000..6c9b498b9d --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.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 { Component, ElementRef, Input } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; +import { FormGroup } from '@angular/forms'; + +@Component({ + moduleId: module.id, + selector: 'checkbox-widget', + templateUrl: './checkbox.widget.html' +}) +export class CheckboxWidget extends WidgetComponent { + + @Input() + field: any; + + @Input('group') + public formGroup: FormGroup; + + @Input('controllerName') + public controllerName: string; + + constructor(public elementRef: ElementRef) { + super(); + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.css new file mode 100644 index 0000000000..384c81f215 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.css @@ -0,0 +1,3 @@ +.date-picker-mdl { + margin-left: 20px; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.html new file mode 100644 index 0000000000..e6b3c60e37 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.html @@ -0,0 +1,48 @@ +
+
+ + Start date must be less than End date + +
+
+
+ + + + Start is required + +
+
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+

FormGroup : {{dateRange && dateRange.value | json }}

+

FormGroup valid : {{dateRange && dateRange.valid }}

+

FormGroup status : {{dateRange && dateRange.errors | json }}

+

FormGroup start status : {{dateRange && dateRange.controls.startDate && dateRange.controls.startDate.errors | json }}

+

FormGroup end status: {{dateRange && dateRange.controls.endDate.errors | json }}

+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.ts new file mode 100644 index 0000000000..6b781fd1f8 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.ts @@ -0,0 +1,180 @@ +/*! + * @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, ViewChild, ElementRef } from '@angular/core'; +import { AbstractControl, FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms'; +import { WidgetComponent } from './../widget.component'; +import * as moment from 'moment'; + +function dateCheck(c: AbstractControl) { + let startDate = moment(c.get('startDate').value); + let endDate = moment(c.get('endDate').value); + let result = startDate.isAfter(endDate); + return result ? {'greaterThan': true} : null; +} + +@Component({ + moduleId: module.id, + selector: 'date-range-widget', + templateUrl: './date-range.widget.html', + styleUrls: ['./date-range.widget.css'] +}) +export class DateRangeWidget extends WidgetComponent { + + public static FORMAT_DATE_ACTIVITI: string = 'YYYY-MM-DD'; + + @ViewChild('startElement') + startElement: any; + + @ViewChild('endElement') + endElement: any; + + @Input('group') + public dateRange: FormGroup; + + @Input() + field: any; + + @Output() + dateRangeChanged: EventEmitter = new EventEmitter(); + + debug: boolean = false; + + dialogStart: any = new mdDateTimePicker.default({ + type: 'date', + future: moment().add(21, 'years') + }); + + dialogEnd: any = new mdDateTimePicker.default({ + type: 'date', + future: moment().add(21, 'years') + }); + + constructor(public elementRef: ElementRef, + private formBuilder: FormBuilder) { + super(); + } + + ngOnInit() { + this.initForm(); + this.addAccessibilityLabelToDatePicker(); + this.initSartDateDialog(); + this.initEndDateDialog(); + } + + initForm() { + let today = moment().format('YYYY-MM-DD'); + + let startDateControl = new FormControl(today); + startDateControl.setValidators(Validators.required); + this.dateRange.addControl('startDate', startDateControl); + + let endDateControl = new FormControl(today); + endDateControl.setValidators(Validators.required); + this.dateRange.addControl('endDate', endDateControl); + + this.dateRange.setValidators(dateCheck); + this.dateRange.valueChanges.subscribe(data => this.onGroupValueChanged(data)); + } + + initSartDateDialog() { + this.dialogStart.trigger = this.startElement.nativeElement; + + let startDateButton = document.getElementById('startDateButton'); + startDateButton.addEventListener('click', () => { + this.dialogStart.toggle(); + }); + } + + private addAccessibilityLabelToDatePicker() { + let left: any = document.querySelector('#mddtp-date__left'); + if (left) { + left.appendChild(this.createCustomElement('date left')); + } + + let right: any = document.querySelector('#mddtp-date__right'); + if (right) { + right.appendChild(this.createCustomElement('date right')); + } + + let cancel: any = document.querySelector('#mddtp-date__cancel'); + if (cancel) { + cancel.appendChild(this.createCustomElement('date cancel')); + } + + let ok: any = document.querySelector('#mddtp-date__ok'); + if (ok) { + ok.appendChild(this.createCustomElement('date ok')); + } + } + + private createCustomElement(text: string): HTMLElement { + let span = document.createElement('span'); + span.style.visibility = 'hidden'; + let rightSpanText = document.createTextNode(text); + span.appendChild(rightSpanText); + return span; + } + + initEndDateDialog() { + this.dialogEnd.trigger = this.endElement.nativeElement; + + let endDateButton = document.getElementById('endDateButton'); + endDateButton.addEventListener('click', () => { + this.dialogEnd.toggle(); + }); + } + + onOkStart(inputEl: HTMLInputElement) { + let date = this.dialogStart.time.format(DateRangeWidget.FORMAT_DATE_ACTIVITI); + this.dateRange.patchValue({ + startDate: date + }); + let materialElemen: any = inputEl.parentElement; + if (materialElemen) { + materialElemen.MaterialTextfield.change(date); + } + } + + onOkEnd(inputEl: HTMLInputElement) { + let date = this.dialogEnd.time.format(DateRangeWidget.FORMAT_DATE_ACTIVITI); + this.dateRange.patchValue({ + endDate: date + }); + + let materialElemen: any = inputEl.parentElement; + if (materialElemen) { + materialElemen.MaterialTextfield.change(date); + } + } + + onGroupValueChanged(data: any) { + if (this.dateRange.valid) { + let dateStart = this.convertMomentDate(this.dateRange.controls['startDate'].value); + let endStart = this.convertMomentDate(this.dateRange.controls['endDate'].value); + this.dateRangeChanged.emit({startDate: dateStart, endDate: endStart}); + } + } + + public convertMomentDate(date: string) { + return moment(date, DateRangeWidget.FORMAT_DATE_ACTIVITI, true).format(DateRangeWidget.FORMAT_DATE_ACTIVITI) + 'T00:00:00.000Z'; + } + + ngOnDestroy() { + + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.css new file mode 100644 index 0000000000..ae995ca854 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.css @@ -0,0 +1,23 @@ +.dropdown-widget { + width: 100%; +} + +.dropdown-widget__select { + width: 100%; +} + +.dropdown-widget__invalid .dropdown-widget__select { + border-color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label { + color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label:after { + background-color: #d50000; +} + +.dropdown-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.html new file mode 100644 index 0000000000..8ab003b892 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.ts new file mode 100644 index 0000000000..4e9450b361 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.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 { Component, Input, Output, EventEmitter } from '@angular/core'; +import { FormGroup, Validators } from '@angular/forms'; +import { WidgetComponent } from './../widget.component'; + +@Component({ + moduleId: module.id, + selector: 'dropdown-widget', + templateUrl: './dropdown.widget.html', + styleUrls: ['./dropdown.widget.css'] +}) +export class DropdownWidget extends WidgetComponent { + + @Input() + field: any; + + @Input('group') + public formGroup: FormGroup; + + @Input('controllerName') + public controllerName: string; + + @Output() + fieldChanged: EventEmitter = new EventEmitter(); + + @Input() + showDefaultOption: boolean = true; + + @Input() + required: boolean = false; + + @Input() + defaultOptionText: string = 'Choose One'; + + constructor() { + super(); + } + + ngOnInit() { + if (this.required) { + this.formGroup.get(this.controllerName).setValidators(Validators.required); + } + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.css new file mode 100644 index 0000000000..3e4f6952f3 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.css @@ -0,0 +1,19 @@ +.number-widget { + width: 100%; +} + +.number-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.html new file mode 100644 index 0000000000..0a3668f160 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.html @@ -0,0 +1,21 @@ +
+
+
+ + +
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.ts new file mode 100644 index 0000000000..0f1e2efbf0 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.ts @@ -0,0 +1,81 @@ +/*! + * @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, ElementRef, OnInit, Input } from '@angular/core'; +import { NumberWidget } from './../number/number.widget'; +import { ReportParameterDetailsModel, ParameterValueModel } from './../../../models/report.model'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +@Component({ + moduleId: module.id, + selector: 'duration-widget', + templateUrl: './duration.widget.html', + styleUrls: ['./duration.widget.css'] +}) +export class DurationWidget extends NumberWidget implements OnInit { + + @Input() + field: any; + + @Input('group') + public formGroup: FormGroup; + + @Input('controllerName') + public controllerName: string; + + @Input() + required: boolean = false; + + duration: ReportParameterDetailsModel; + currentValue: number; + + public selectionGroup: FormGroup; + + constructor(public elementRef: ElementRef) { + super(elementRef); + } + + ngOnInit() { + let timeType = new FormControl(); + this.formGroup.addControl('timeType', timeType); + + if (this.required) { + this.formGroup.get(this.controllerName).setValidators(Validators.required); + } + if (this.field.value === null) { + this.field.value = 0; + } + + let paramOptions: ParameterValueModel[] = []; + paramOptions.push(new ParameterValueModel({id: '1', name: 'Seconds'})); + paramOptions.push(new ParameterValueModel({id: '60', name: 'Minutes'})); + paramOptions.push(new ParameterValueModel({id: '3600', name: 'Hours'})); + paramOptions.push(new ParameterValueModel({id: '86400', name: 'Days', selected: true})); + + this.duration = new ReportParameterDetailsModel({id: 'duration', name: 'duration', options: paramOptions}); + this.duration.value = paramOptions[0].id; + } + + public calculateDuration() { + if (this.field && this.duration.value ) { + this.currentValue = parseInt(this.field.value, 10) * parseInt(this.duration.value, 10); + this.formGroup.get(this.controllerName).setValue(this.currentValue); + this.fieldChanged.emit({value: this.currentValue}); + } + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/index.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/index.ts new file mode 100644 index 0000000000..c044bc245b --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/index.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 { DropdownWidget } from './dropdown/dropdown.widget'; +import { NumberWidget } from './number/number.widget'; +import { DurationWidget } from './duration/duration.widget'; +import { CheckboxWidget } from './checkbox/checkbox.widget'; +import { DateRangeWidget } from './date-range/date-range.widget'; + +// primitives +export * from './dropdown/dropdown.widget'; +export * from './number/number.widget'; +export * from './duration/duration.widget'; +export * from './checkbox/checkbox.widget'; +export * from './date-range/date-range.widget'; + +export const WIDGET_DIRECTIVES: any[] = [ + DropdownWidget, + NumberWidget, + DurationWidget, + CheckboxWidget, + DateRangeWidget +]; diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.css new file mode 100644 index 0000000000..3e4f6952f3 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.css @@ -0,0 +1,19 @@ +.number-widget { + width: 100%; +} + +.number-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.html new file mode 100644 index 0000000000..8219909603 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.html @@ -0,0 +1,10 @@ +
+ + +
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.ts new file mode 100644 index 0000000000..df0b2cea95 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.ts @@ -0,0 +1,66 @@ +/*! + * @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, ElementRef, Input } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; +import { FormGroup, Validators } from '@angular/forms'; + +@Component({ + moduleId: module.id, + selector: 'number-widget', + templateUrl: './number.widget.html', + styleUrls: ['./number.widget.css'] +}) +export class NumberWidget extends WidgetComponent { + + @Input() + field: any; + + @Input('group') + public formGroup: FormGroup; + + @Input('controllerName') + public controllerName: string; + + @Input() + required: boolean = false; + + constructor(public elementRef: ElementRef) { + super(); + } + + ngOnInit() { + if (this.required) { + this.formGroup.get(this.controllerName).setValidators(Validators.required); + } + } + + setupMaterialComponents(handler: any): boolean { + // workaround for MDL issues with dynamic components + if (handler) { + handler.upgradeAllRegistered(); + if (this.elementRef && this.hasValue()) { + let container = this.elementRef.nativeElement.querySelector('.mdl-textfield'); + if (container && container.MaterialTextfield) { + container.MaterialTextfield.change(this.field.value.toString()); + } + } + return true; + } + return false; + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/widget.component.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/widget.component.ts new file mode 100644 index 0000000000..dbab34a340 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/widget.component.ts @@ -0,0 +1,66 @@ +/*! + * @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, SimpleChanges, OnChanges } from '@angular/core'; + +/** + * Base widget component. + */ +export class WidgetComponent implements AfterViewInit, OnChanges { + + @Input() + field: any; + + @Output() + fieldChanged: EventEmitter = new EventEmitter(); + + ngOnChanges(changes: SimpleChanges) { + let field = changes['field']; + if (field && field.currentValue) { + this.fieldChanged.emit(field.currentValue.value); + return; + } + } + + hasField() { + return this.field ? true : false; + } + + hasValue(): boolean { + return this.field && + this.field.value !== null && + this.field.value !== undefined; + } + + changeValue(field: any) { + this.fieldChanged.emit(field); + } + + ngAfterViewInit() { + this.setupMaterialComponents(componentHandler); + } + + setupMaterialComponents(handler?: any): boolean { + // workaround for MDL issues with dynamic components + if (handler) { + handler.upgradeAllRegistered(); + return true; + } + return false; + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/declarations.d.ts b/ng2-components/ng2-activiti-analytics/src/declarations.d.ts new file mode 100644 index 0000000000..a4cceff023 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/declarations.d.ts @@ -0,0 +1,23 @@ +/*! + * @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. + */ + +declare let moment: any; +declare let mdDateTimePicker: any; + +// MDL +declare let componentHandler: any; +declare let dialogPolyfill: any; diff --git a/ng2-components/ng2-activiti-analytics/src/i18n/en.json b/ng2-components/ng2-activiti-analytics/src/i18n/en.json new file mode 100644 index 0000000000..1dce1a9c60 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/i18n/en.json @@ -0,0 +1,45 @@ +{ + "ANALYTICS": { + "TTILE": "ANALYTICS", + "MESSAGES": { + "UNKNOWN-WIDGET-TYPE": "UNKNOWN WIDGET TYPE", + "FILL-PARAMETER": "Fill in the parameters to generate your report", + "NO-DATA-FOUND": "No data found" + } + }, + "__KEY_REPORTING": { + "DEFAULT-REPORTS": { + "PROCESS-DEFINITION-OVERVIEW": { + "GENERAL-TABLE-TOTAL-PROCESS-DEFINITIONS": "Total number of process definitions", + "GENERAL-TABLE-TOTAL-PROCESS-INSTANCES": "Total number of process instances", + "GENERAL-TABLE-ACTIVE-PROCESS-INSTANCES": "Total number of active process instances", + "GENERAL-TABLE-COMPLETED-PROCESS-INSTANCES": "Total number of completed process instances" + } + } + }, + "REPORTING": { + "DEFAULT-REPORTS": { + "PROCESS-HEAT-MAP": { + "TYPE-FILTERING": "Include all process steps (Unchecking this, will remove pass through steps like start events, gateways, etc.)?" + }, + "PROCESS-INSTANCES-OVERVIEW": { + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "SLOW-PROC-INST-NUMBER": "How many of the slowest process instances should be displayed?" + }, + "TASK-OVERVIEW": { + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "DATE-RANGE-INTERVAL": "Aggregate dates by" + }, + "TASK-SLA": { + "TASK": "Task", + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "SLA-DURATION": "What is the time this task needs to be completed in to be within the SLA?" + } + }, + "PROCESS-STATUS": "Process status", + "TASK-STATUS": "Task status" + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/i18n/it.json b/ng2-components/ng2-activiti-analytics/src/i18n/it.json new file mode 100644 index 0000000000..15c05b6832 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/i18n/it.json @@ -0,0 +1,5 @@ +{ + "ANALYTICS": { + "TTILE": "ANALYTICS" + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts b/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts new file mode 100644 index 0000000000..658f2e34f1 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts @@ -0,0 +1,240 @@ +/*! + * @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 Chart { + id: string; + type: string; + + constructor(obj?: any) { + this.id = obj && obj.id || null; + if (obj && obj.type) { + this.type = this.convertType(obj.type); + } + } + + private convertType(type: string) { + let chartType = ''; + switch (type) { + case 'pieChart': + chartType = 'pie'; + break; + case 'table': + chartType = 'table'; + break; + case 'line': + chartType = 'line'; + break; + case 'barChart': + chartType = 'bar'; + break; + case 'multiBarChart': + chartType = 'multiBar'; + break; + case 'processDefinitionHeatMap': + chartType = 'HeatMap'; + break; + default: + chartType = 'table'; + break; + } + return chartType; + } +} + +export class LineChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.labels = obj && obj.columnNames.slice(1, obj.columnNames.length); + + obj.rows.forEach((value: any) => { + this.datasets.push({data: value.slice(1, value.length), label: value[0]}); + }); + } +} + +export class BarChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + data: any[] = []; + xAxisType: string; + yAxisType: string; + options: any = { + responsive: true, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true, + stepSize: 1 + } + }], + xAxes: [{ + ticks: { + }, + stacked: false + }] + } + }; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.xAxisType = obj && obj.xAxisType || null; + this.yAxisType = obj && obj.yAxisType || null; + this.options.scales.xAxes[0].ticks.callback = this.xAxisTickFormatFunction(this.xAxisType); + this.options.scales.yAxes[0].ticks.callback = this.yAxisTickFormatFunction(this.yAxisType); + if (obj.values) { + obj.values.forEach((params: any) => { + let dataValue = []; + params.values.forEach((info: any) => { + info.forEach((value: any, index: any) => { + if (index % 2 === 0) { + if (!this.labels.includes(value)) { + this.labels.push(value); + } + } else { + dataValue.push(value); + } + }); + }); + if (dataValue && dataValue.length > 0) { + this.datasets.push({data: dataValue, label: params.key}); + } + }); + } + } + + xAxisTickFormatFunction = function (xAxisType) { + return function (value) { + if (xAxisType !== null && xAxisType !== undefined) { + if ('date_day' === xAxisType) { + return moment(new Date(value)).format('DD'); + } else if ('date_month' === xAxisType) { + return moment(new Date(value)).format('MMMM'); + } else if ('date_year' === xAxisType) { + return moment(new Date(value)).format('YYYY'); + } + } + return value; + }; + }; + + yAxisTickFormatFunction = function (yAxisType) { + return function (value) { + if (yAxisType !== null && yAxisType !== undefined) { + if ('count' === yAxisType) { + let label = '' + value; + if (label.indexOf('.') !== -1) { + return ''; + } + } + } + return value; + }; + }; + + hasDatasets() { + return this.datasets && this.datasets.length > 0 ? true : false; + } +} + +export class MultiBarChart extends BarChart { + + constructor(obj?: any) { + super(obj); + } +} + +export class TableChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.labels = obj && obj.columnNames; + if (obj.rows) { + this.datasets = obj && obj.rows; + } + } + + hasDatasets() { + return this.datasets && this.datasets.length > 0 ? true : false; + } +} + +export class HeatMapChart extends Chart { + avgTimePercentages: string; + avgTimeValues: string; + processDefinitionId: string; + titleKey: string; + totalCountValues: string; + totalCountsPercentages: string; + totalTimePercentages: string; + totalTimeValues: string; + + constructor(obj?: any) { + super(obj); + this.avgTimePercentages = obj && obj.avgTimePercentages || null; + this.avgTimeValues = obj && obj.avgTimeValues || null; + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.totalCountValues = obj && obj.totalCountValues || null; + this.titleKey = obj && obj.titleKey || null; + this.totalCountsPercentages = obj && obj.totalCountsPercentages || null; + this.totalTimePercentages = obj && obj.totalTimePercentages || null; + this.totalTimeValues = obj && obj.totalTimeValues || null; + } +} + +export class PieChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + data: string[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + if (obj.values) { + obj.values.forEach((value: any) => { + this.add(value.key, value.y); + }); + } + } + + add(label: string, data: string) { + this.labels.push(label); + this.data.push(data); + } + + hasData() { + return this.data && this.data.length > 0 ? true : false; + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/models/report.model.ts b/ng2-components/ng2-activiti-analytics/src/models/report.model.ts new file mode 100644 index 0000000000..54ef72c176 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/models/report.model.ts @@ -0,0 +1,139 @@ +/*! + * @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 report definition. + * + * + * @returns {ReportParametersModel} . + */ +export class ReportParametersModel { + id: number; + name: string; + definition: ReportDefinitionModel; + created: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + if (obj && obj.definition) { + this.definition = new ReportDefinitionModel(JSON.parse(obj.definition)); + } + this.created = obj && obj.created || null; + } + + hasParameters() { + return (this.definition && this.definition.parameters && this.definition.parameters.length > 0) ? true : false; + } +} + +export class ReportDefinitionModel { + parameters: ReportParameterDetailsModel[] = []; + + constructor(obj?: any) { + obj.parameters.forEach((params: any) => { + let reportParamsModel = new ReportParameterDetailsModel(params); + this.parameters.push(reportParamsModel); + }); + } + + findParam(name: string): ReportParameterDetailsModel { + this.parameters.forEach((param) => { + return param.type === name ? param : null; + }); + return null; + } +} + +/** + * + * This object represent the report parameter definition. + * + * + * @returns {ReportParameterDetailsModel} . + */ +export class ReportParameterDetailsModel { + id: string; + name: string; + nameKey: string; + type: string; + value: string; + options: ParameterValueModel[]; + dependsOn: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + this.nameKey = obj && obj.nameKey || null; + this.type = obj && obj.type || null; + this.value = obj && obj.value || null; + this.options = obj && obj.options || null; + this.dependsOn = obj && obj.dependsOn || null; + } +} + +export class ParameterValueModel { + id: string; + name: string; + version: string; + value: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + this.value = obj && obj.value || null; + this.version = obj && obj.version || null; + } + + get label () { + return this.version ? `${this.name} (v ${this.version}) ` : this.name; + } +} + +export class ReportQuery { + processDefinitionId: string; + status: string; + taskName: string; + typeFiltering: boolean; + dateRange: ReportDateRange; + dateRangeInterval: string; + slowProcessInstanceInteger: number; + duration: number; + + constructor(obj?: any) { + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.status = obj && obj.status || null; + this.taskName = obj && obj.taskName || null; + this.dateRangeInterval = obj && obj.dateRangeInterval || null; + this.typeFiltering = obj && obj.typeFiltering || true; + this.slowProcessInstanceInteger = obj && obj.slowProcessInstanceInteger || 0; + this.duration = obj && obj.duration || 0; + this.dateRange = new ReportDateRange(obj); + } +} + +export class ReportDateRange { + startDate: string; + endDate: string; + + constructor(obj?: any) { + this.startDate = obj && obj.startDate || null; + this.endDate = obj && obj.endDate || null; + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts b/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts new file mode 100644 index 0000000000..4e3f81dc55 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/services/analytics.service.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 { Injectable } from '@angular/core'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { Observable } from 'rxjs/Rx'; +import { Response, Http, Headers, RequestOptions, URLSearchParams } from '@angular/http'; +import { ReportParametersModel, ParameterValueModel } from '../models/report.model'; +import { Chart, PieChart, TableChart, BarChart, HeatMapChart, MultiBarChart } from '../models/chart.model'; + +@Injectable() +export class AnalyticsService { + + constructor(private authService: AlfrescoAuthenticationService, + private http: Http, + private alfrescoSettingsService: AlfrescoSettingsService) { + } + + /** + * Retrive all the Deployed app + * @returns {Observable} + */ + getReportList(): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/reports`; + let options = this.getRequestOptions(); + return this.http + .get(url, options) + .map((res: any) => { + let reports: ReportParametersModel[] = []; + let body = res.json(); + body.forEach((report: ReportParametersModel) => { + let reportModel = new ReportParametersModel(report); + reports.push(reportModel); + }); + return reports; + }).catch(this.handleError); + } + + getReportParams(reportId: string): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/report-params/${reportId}`; + let options = this.getRequestOptions(); + return this.http + .get(url, options) + .map((res: any) => { + let body = res.json(); + return new ReportParametersModel(body); + }).catch(this.handleError); + } + + getParamValuesByType(type: string, appId: string, reportId?: string, processDefinitionId?: string) { + if (type === 'status') { + return this.getProcessStatusValues(); + } else if (type === 'processDefinition') { + if (appId === null || appId === undefined) { + return this.getProcessDefinitionsValuesNoApp(); + } else { + return this.getProcessDefinitionsValues(appId); + } + } else if (type === 'dateInterval') { + return this.getDateIntervalValues(); + } else if (type === 'task') { + return this.getTasksByProcessDefinitionId(reportId, processDefinitionId); + } else { + return Observable.create(observer => { + observer.next(null); + observer.complete(); + }); + } + } + + getProcessStatusValues(): Observable { + let paramOptions: ParameterValueModel[] = []; + + paramOptions.push(new ParameterValueModel({ id: 'All', name: 'All' })); + paramOptions.push(new ParameterValueModel({ id: 'Active', name: 'Active' })); + paramOptions.push(new ParameterValueModel({ id: 'Complete', name: 'Complete' })); + + return Observable.create(observer => { + observer.next(paramOptions); + observer.complete(); + }); + } + + getDateIntervalValues(): Observable { + let paramOptions: ParameterValueModel[] = []; + + paramOptions.push(new ParameterValueModel({ id: 'byHour', name: 'By hour' })); + paramOptions.push(new ParameterValueModel({ id: 'byDay', name: 'By day' })); + paramOptions.push(new ParameterValueModel({ id: 'byWeek', name: 'By week' })); + paramOptions.push(new ParameterValueModel({ id: 'byMonth', name: 'By month' })); + paramOptions.push(new ParameterValueModel({ id: 'byYear', name: 'By year' })); + + return Observable.create(observer => { + observer.next(paramOptions); + observer.complete(); + }); + } + + getMetricValues(): Observable { + let paramOptions: ParameterValueModel[] = []; + + paramOptions.push(new ParameterValueModel({ id: 'totalCount', name: 'Number of times a step is executed' })); + paramOptions.push(new ParameterValueModel({ id: 'totalTime', name: 'Total time spent in a process step' })); + paramOptions.push(new ParameterValueModel({ id: 'avgTime', name: 'Average time spent in a process step' })); + + return Observable.create(observer => { + observer.next(paramOptions); + observer.complete(); + }); + } + + getProcessDefinitionsValuesNoApp(): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/process-definitions`; + let options = this.getRequestOptions(); + return this.http + .get(url, options) + .map((res: any) => { + let paramOptions: ParameterValueModel[] = []; + let body = res.json(); + body.forEach((opt) => { + paramOptions.push(new ParameterValueModel(opt)); + }); + return paramOptions; + }).catch(this.handleError); + } + + getProcessDefinitionsValues(appId: string): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/process-definitions`; + let params: URLSearchParams; + params = new URLSearchParams(); + params.set('appDefinitionId', appId); + let options = this.getRequestOptions(params); + return this.http + .get(url, options) + .map((res: any) => { + let paramOptions: ParameterValueModel[] = []; + let body = res.json(); + body.data.forEach((opt) => { + paramOptions.push(new ParameterValueModel(opt)); + }); + return paramOptions; + }).catch(this.handleError); + } + + getTasksByProcessDefinitionId(reportId: string, processDefinitionId: string): Observable { + if (reportId && processDefinitionId) { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/report-params/${reportId}/tasks`; + let params: URLSearchParams; + if (processDefinitionId) { + params = new URLSearchParams(); + params.set('processDefinitionId', processDefinitionId); + } + let options = this.getRequestOptions(params); + return this.http + .get(url, options) + .map((res: any) => { + let paramOptions: ParameterValueModel[] = []; + let body = res.json(); + body.forEach((opt) => { + paramOptions.push(new ParameterValueModel({ id: opt, name: opt })); + }); + return paramOptions; + }).catch(this.handleError); + } else { + return Observable.create(observer => { + observer.next(null); + observer.complete(); + }); + } + } + + getReportsByParams(reportId: number, paramsQuery: any): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/report-params/${reportId}`; + let body = paramsQuery ? JSON.stringify(paramsQuery) : {}; + let options = this.getRequestOptions(); + return this.http + .post(url, body, options) + .map((res: any) => { + let elements: Chart[] = []; + let bodyRes = res.json(); + bodyRes.elements.forEach((chartData) => { + if (chartData.type === 'pieChart') { + elements.push(new PieChart(chartData)); + } else if (chartData.type === 'table') { + elements.push(new TableChart(chartData)); + } else if (chartData.type === 'processDefinitionHeatMap') { + elements.push(new HeatMapChart(chartData)); + } else if (chartData.type === 'masterDetailTable') { + elements.push(new TableChart(chartData)); + } else if (chartData.type === 'barChart') { + elements.push(new BarChart(chartData)); + } else if (chartData.type === 'multiBarChart') { + elements.push(new MultiBarChart(chartData)); + } + }); + + return elements; + }).catch(this.handleError); + } + + public createDefaultReports(): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/default-reports`; + let options = this.getRequestOptions(); + let body = {}; + return this.http + .post(url, body, options) + .map((res: any) => { + return res; + }).catch(this.handleError); + } + + public getHeaders(): Headers { + return new Headers({ + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': this.authService.getTicketBpm() + }); + } + + public getRequestOptions(param?: any): RequestOptions { + let headers = this.getHeaders(); + return new RequestOptions({ headers: headers, withCredentials: true, search: param }); + } + + private handleError(error: Response) { + console.error(error); + return Observable.throw(error.json().error || 'Server error'); + } +} diff --git a/ng2-components/ng2-activiti-analytics/tsconfig.json b/ng2-components/ng2-activiti-analytics/tsconfig.json new file mode 100644 index 0000000000..7be35bfec8 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "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", + "types": ["core-js", "jasmine", "node"] + }, + "exclude": [ + "demo", + "node_modules", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-analytics/tslint.json b/ng2-components/ng2-activiti-analytics/tslint.json new file mode 100644 index 0000000000..27e0dd81da --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/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": true, + "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-diagrams/.editorconfig b/ng2-components/ng2-activiti-diagrams/.editorconfig new file mode 100644 index 0000000000..75a2477db7 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/.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-diagrams/.gitignore b/ng2-components/ng2-activiti-diagrams/.gitignore new file mode 100644 index 0000000000..fe20e77f42 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/.gitignore @@ -0,0 +1,15 @@ +npm-debug.log +node_modules +.idea +typings +coverage +dist +src/**/*.js +src/**/*.js.map + +demo/**/*.js +demo/**/*.js.map +demo/**/*.d.ts +index.js +index.js.map +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-diagrams/.npmignore b/ng2-components/ng2-activiti-diagrams/.npmignore new file mode 100644 index 0000000000..c5ca623298 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/.npmignore @@ -0,0 +1,15 @@ +npm-debug.log +.idea + +coverage/ +node_modules +typings/ +fonts/ + +/.editorconfig +/.travis.yml +/*.js +/*.json +/*.ts +/*.js.map +/.npmignore diff --git a/ng2-components/ng2-activiti-diagrams/LICENSE b/ng2-components/ng2-activiti-diagrams/LICENSE new file mode 100644 index 0000000000..430d42bbea --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/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-diagrams/README.md b/ng2-components/ng2-activiti-diagrams/README.md new file mode 100644 index 0000000000..5192d5647a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/README.md @@ -0,0 +1,226 @@ +# Activiti Diagrams Component for Angular 2 +

+ + travis
+    Status + + + travis
+    Status + + + Coverage Status + + + npm downloads + + + license + + + alfresco component + + + angular 2 + + + typescript + + + node version + +

+ +## 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 + +Follow the 3 steps below: + +1. Npm + + ```sh + npm install ng2-activiti-diagrams --save + ``` + +2. Html + + Include these dependencies in your index.html page: + + ```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + +3. SystemJs + + Add the following components to your systemjs.config.js file: + + - ng2-translat + - ng2-alfresco-core + - alfresco-js-api + - ng2-activiti-diagrams + - raphael + + Please refer to the following example file: [systemjs.config.js](demo/systemjs + .config.js) . + +## Basic usage example Activiti Diagrams + +This component shows the diagram of a process. + +```html + +``` + +Usage example of this component : + +**main.ts** +```ts + +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { DiagramsModule } from 'ng2-activiti-diagrams'; + +@Component({ + selector: 'activiti-diagrams-demo', + template: `` +}) + +export class DiagramDemoComponent { + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = 'http://localhost:9999'; + + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + }, + error => { + console.log(error); + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + DiagramsModule + ], + declarations: [ DiagramDemoComponent ], + bootstrap: [ DiagramDemoComponent ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); + +``` + +#### Events + +| Name | Description | +| --- | --- | +| `onSuccess` | The event is emitted when the diagrams element are loaded | +| `onError` | The event is emitted when the an error occur during the loading | + +#### Options + +| Name | Description | +| --- | --- | +| `metricPercentage` | The array that contains the percentage of the time for each element | + +## 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 +``` + +## Demo + +If you want have a demo of how the component works, please check the demo folder : + +```sh +cd demo +npm install +npm start +``` + +## NPM scripts + +| Command | Description | +| --- | --- | +| npm run build | Build component | +| npm run build:w | Build component and keep watching the changes | +| npm run test | Run unit tests in the console | +| npm run test-browser | Run unit tests in the browser +| npm run coverage | Run unit tests and display code coverage report | + +## License + +[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE) \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/assets/Polyline.js b/ng2-components/ng2-activiti-diagrams/assets/Polyline.js new file mode 100644 index 0000000000..9983929cf1 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/assets/Polyline.js @@ -0,0 +1,372 @@ +/** + * Class to generate polyline + * + * @author Dmitry Farafonov + */ + +var ANCHOR_TYPE= { + main: "main", + middle: "middle", + first: "first", + last: "last" +}; + +function Anchor(uuid, type, x, y) { + this.uuid = uuid; + this.x = x; + this.y = y; + this.type = (type == ANCHOR_TYPE.middle) ? ANCHOR_TYPE.middle : ANCHOR_TYPE.main; +}; +Anchor.prototype = { + uuid: null, + x: 0, + y: 0, + type: ANCHOR_TYPE.main, + isFirst: false, + isLast: false, + ndex: 0, + typeIndex: 0 +}; + +function Polyline(uuid, points, strokeWidth, paper) { + /* Array on coordinates: + * points: [{x: 410, y: 110}, 1 + * {x: 570, y: 110}, 1 2 + * {x: 620, y: 240}, 2 3 + * {x: 750, y: 270}, 3 4 + * {x: 650, y: 370}]; 4 + */ + this.points = points; + + /* + * path for graph + * [["M", x1, y1], ["L", x2, y2], ["C", ax, ay, bx, by, x3, y3], ["L", x3, y3]] + */ + this.path = []; + + this.anchors = []; + + if (strokeWidth) this.strokeWidth = strokeWidth; + + this.paper = paper; + + this.closePath = false; + + this.init(); +}; + +Polyline.prototype = { + id: null, + points: [], + path: [], + anchors: [], + strokeWidth: 1, + radius: 1, + showDetails: false, + paper: null, + element: null, + isDefaultConditionAvailable: false, + closePath: false, + + init: function(points){ + var linesCount = this.getLinesCount(); + if (linesCount < 1) + return; + + this.normalizeCoordinates(); + + // create anchors + + this.pushAnchor(ANCHOR_TYPE.first, this.getLine(0).x1, this.getLine(0).y1); + + for (var i = 1; i < linesCount; i++) + { + var line1 = this.getLine(i-1); + this.pushAnchor(ANCHOR_TYPE.main, line1.x2, line1.y2); + } + + this.pushAnchor(ANCHOR_TYPE.last, this.getLine(linesCount-1).x2, this.getLine(linesCount-1).y2); + + this.rebuildPath(); + }, + + normalizeCoordinates: function(){ + for(var i=0; i < this.points.length; i++){ + this.points[i].x = parseFloat(this.points[i].x); + this.points[i].y = parseFloat(this.points[i].y); + } + }, + + getLinesCount: function(){ + return this.points.length-1; + }, + _getLine: function(i){ + if (this.points.length > i && this.points[i]) { + return {x1: this.points[i].x, y1: this.points[i].y, x2: this.points[i+1].x, y2: this.points[i+1].y}; + } else { + return undefined; + } + }, + getLine: function(i){ + var line = this._getLine(i); + if (line != undefined) { + line.angle = this.getLineAngle(i); + } + return line; + }, + getLineAngle: function(i){ + var line = this._getLine(i); + return Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + }, + getLineLengthX: function(i){ + var line = this.getLine(i); + return (line.x2 - line.x1); + }, + getLineLengthY: function(i){ + var line = this.getLine(i); + return (line.y2 - line.y1); + }, + getLineLength: function(i){ + return Math.sqrt(Math.pow(this.getLineLengthX(i), 2) + Math.pow(this.getLineLengthY(i), 2)); + }, + + getAnchors: function(){ + return this.anchors; + }, + getAnchorsCount: function(type){ + if (!type) + return this.anchors.length; + else { + var count = 0; + for(var i=0; i < this.getAnchorsCount(); i++){ + var anchor = this.anchors[i]; + if (anchor.getType() == type) { + count++; + } + } + return count; + } + }, + + pushAnchor: function(type, x, y, index){ + if (type == ANCHOR_TYPE.first) { + index = 0; + typeIndex = 0; + } else if (type == ANCHOR_TYPE.last) { + index = this.getAnchorsCount(); + typeIndex = 0; + } else if (!index) { + index = this.anchors.length; + } else { + for(var i=0; i < this.getAnchorsCount(); i++){ + var anchor = this.anchors[i]; + if (anchor.index > index) { + anchor.index++; + anchor.typeIndex++; + } + } + } + + var anchor = new Anchor(this.id, ANCHOR_TYPE.main, x, y, index, typeIndex); + + this.anchors.push(anchor); + }, + + getAnchor: function(position){ + return this.anchors[position]; + }, + + getAnchorByType: function(type, position){ + if (type == ANCHOR_TYPE.first) + return this.anchors[0]; + if (type == ANCHOR_TYPE.last) + return this.anchors[this.getAnchorsCount()-1]; + + for(var i=0; i < this.getAnchorsCount(); i++){ + var anchor = this.anchors[i]; + if (anchor.type == type) { + if( position == anchor.position) + return anchor; + } + } + return null; + }, + + addNewPoint: function(position, x, y){ + // + for(var i = 0; i < this.getLinesCount(); i++){ + var line = this.getLine(i); + if (x > line.x1 && x < line.x2 && y > line.y1 && y < line.y2) { + this.points.splice(i+1,0,{x: x, y: y}); + break; + } + } + + this.rebuildPath(); + }, + + rebuildPath: function(){ + var path = []; + + for(var i = 0; i < this.getAnchorsCount(); i++){ + var anchor = this.getAnchor(i); + + var pathType = ""; + if (i == 0) + pathType = "M"; + else + pathType = "L"; + + // TODO: save previous points and calculate new path just if points are updated, and then save currents values as previous + + var targetX = anchor.x, targetY = anchor.y; + if (i>0 && i < this.getAnchorsCount()-1) { + // get new x,y + var cx = anchor.x, cy = anchor.y; + + // pivot point of prev line + var AO = this.getLineLength(i-1); + if (AO < this.radius) { + AO = this.radius; + } + + this.isDefaultConditionAvailable = (this.isDefaultConditionAvailable || (i == 1 && AO > 10)); + + var ED = this.getLineLengthY(i-1) * this.radius / AO; + var OD = this.getLineLengthX(i-1) * this.radius / AO; + targetX = anchor.x - OD; + targetY = anchor.y - ED; + + if (AO < 2*this.radius && i>1) { + targetX = anchor.x - this.getLineLengthX(i-1)/2; + targetY = anchor.y - this.getLineLengthY(i-1)/2;; + } + + // pivot point of next line + var AO = this.getLineLength(i); + if (AO < this.radius) { + AO = this.radius; + } + var ED = this.getLineLengthY(i) * this.radius / AO; + var OD = this.getLineLengthX(i) * this.radius / AO; + var nextSrcX = anchor.x + OD; + var nextSrcY = anchor.y + ED; + + if (AO < 2*this.radius && i 10)); + } + + // anti smoothing + if (this.strokeWidth%2 == 1) { + targetX += 0.5; + targetY += 0.5; + } + + path.push([pathType, targetX, targetY]); + + if (i>0 && i < this.getAnchorsCount()-1) { + path.push(["C", ax, ay, bx, by, zx, zy]); + } + } + + if (this.closePath) + { + path.push(["Z"]); + } + + this.path = path; + }, + + transform: function(transformation) + { + this.element.transform(transformation); + }, + attr: function(attrs) + { + // TODO: foreach and set each + this.element.attr(attrs); + } +}; + +function Polygone(points, strokeWidth) { + /* Array on coordinates: + * points: [{x: 410, y: 110}, 1 + * {x: 570, y: 110}, 1 2 + * {x: 620, y: 240}, 2 3 + * {x: 750, y: 270}, 3 4 + * {x: 650, y: 370}]; 4 + */ + this.points = points; + + /* + * path for graph + * [["M", x1, y1], ["L", x2, y2], ["C", ax, ay, bx, by, x3, y3], ["L", x3, y3]] + */ + this.path = []; + + this.anchors = []; + + if (strokeWidth) this.strokeWidth = strokeWidth; + + this.closePath = true; + this.init(); +}; + + +/* + * Poligone is inherited from Poliline: draws closedPath of polyline + */ + +var Foo = function () { }; +Foo.prototype = Polyline.prototype; + +Polygone.prototype = new Foo(); + +Polygone.prototype.rebuildPath = function(){ + var path = []; + for(var i = 0; i < this.getAnchorsCount(); i++){ + var anchor = this.getAnchor(i); + + var pathType = ""; + if (i == 0) + pathType = "M"; + else + pathType = "L"; + + var targetX = anchor.x, targetY = anchor.y; + + // anti smoothing + if (this.strokeWidth%2 == 1) { + targetX += 0.5; + targetY += 0.5; + } + + path.push([pathType, targetX, targetY]); + } + if (this.closePath) + path.push(["Z"]); + + this.path = path; +}; \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/assets/license_header.txt b/ng2-components/ng2-activiti-diagrams/assets/license_header.txt new file mode 100644 index 0000000000..83fd1531a3 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/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-diagrams/demo/.gitignore b/ng2-components/ng2-activiti-diagrams/demo/.gitignore new file mode 100644 index 0000000000..25beca4c27 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/demo/.gitignore @@ -0,0 +1,6 @@ +node_modules +.idea +coverage +dist +typings +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-diagrams/demo/.npmignore b/ng2-components/ng2-activiti-diagrams/demo/.npmignore new file mode 100644 index 0000000000..c51c008259 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/demo/.npmignore @@ -0,0 +1,3 @@ +node_modules +dist +typings \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/demo/README.md b/ng2-components/ng2-activiti-diagrams/demo/README.md new file mode 100644 index 0000000000..6c99417ed4 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/demo/README.md @@ -0,0 +1,13 @@ +# Activiti Diagrams demo + +Install: + +``` +npm install +``` + +Run the project: + +``` +npm start +``` \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/demo/index.html b/ng2-components/ng2-activiti-diagrams/demo/index.html new file mode 100644 index 0000000000..78a50121bb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/demo/index.html @@ -0,0 +1,49 @@ + + + + + + Alfresco Angular 2 Activiti Diagrams - Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ng2-components/ng2-activiti-diagrams/demo/package.json b/ng2-components/ng2-activiti-diagrams/demo/package.json new file mode 100644 index 0000000000..fb1adf2eb2 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/demo/package.json @@ -0,0 +1,69 @@ +{ + "name": "ng2-activiti-diagrams-demo", + "description": "Alfresco Angular2 Diagrams Component - Demo", + "version": "0.1.0", + "author": "Alfresco Software, Ltd.", + "main": "index.js", + "scripts": { + "clean": "npm install rimraf && rimraf dist node_modules typings dist", + "postinstall": "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 && tsc", + "build:w": "npm run tslint && rimraf dist && tsc -w", + "tsc": "tsc", + "tsc:w": "tsc -w", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Maurizio Vitale", + "email": "maurizio.vitale@alfresco.com" + } + ], + "keywords": [ + "ng2", + "angular", + "angular2", + "activiti", + "activiti-diagrams" + ], + "dependencies": { + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "raphael": "^2.2.6", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-activiti-diagrams": "^0.4.0" + }, + "devDependencies": { + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", + "rimraf": "2.5.2", + "tslint": "^3.8.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" + } +} diff --git a/ng2-components/ng2-activiti-diagrams/demo/src/main.ts b/ng2-components/ng2-activiti-diagrams/demo/src/main.ts new file mode 100644 index 0000000000..e48a224366 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/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 { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { DiagramsModule } from 'ng2-activiti-diagrams'; + +@Component({ + selector: 'alfresco-app-demo', + template: ` +
+
+
+

+
+ Authentication failed to ip {{ host }} with user: admin, admin, you can still try to add a valid ticket to perform + operations. +
+
+ +
+ + ` +}) + +export class DiagramDemoComponent { + + processDefinitionId: string = 'ThirdProcess:1:15053'; + + authenticated: boolean; + + host: string = 'http://localhost:9999'; + + ticket: string; + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = this.host; + settingsService.setProviders('BPM'); + + if (this.authService.getTicketBpm()) { + this.ticket = this.authService.getTicketBpm(); + } + } + + public updateTicket(): void { + localStorage.setItem('ticket-BPM', this.ticket); + } + + public updateHost(): void { + this.settingsService.bpmHost = this.host; + this.login(); + } + + public ngOnInit(): void { + this.login(); + } + + login() { + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + this.ticket = this.authService.getTicketBpm(); + this.authenticated = true; + }, + error => { + console.log(error); + this.authenticated = false; + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + DiagramsModule + ], + declarations: [ DiagramDemoComponent ], + bootstrap: [ DiagramDemoComponent ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/ng2-components/ng2-activiti-diagrams/demo/systemjs.config.js b/ng2-components/ng2-activiti-diagrams/demo/systemjs.config.js new file mode 100644 index 0000000000..07cbc7aeee --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/demo/systemjs.config.js @@ -0,0 +1,47 @@ +/** + * System configuration for Angular 2 samples + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'raphael': 'npm:raphael', + 'ng2-translate': 'npm:ng2-translate', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-activiti-diagrams': 'npm:ng2-activiti-diagrams/dist' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ng2-translate': { defaultExtension: 'js' }, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-diagrams': { main: './index.js', defaultExtension: 'js'} + } + }); +})(this); diff --git a/ng2-components/ng2-activiti-diagrams/demo/tsconfig.json b/ng2-components/ng2-activiti-diagrams/demo/tsconfig.json new file mode 100644 index 0000000000..7be35bfec8 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/demo/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "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", + "types": ["core-js", "jasmine", "node"] + }, + "exclude": [ + "demo", + "node_modules", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-diagrams/demo/tslint.json b/ng2-components/ng2-activiti-diagrams/demo/tslint.json new file mode 100644 index 0000000000..8c9703b9de --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/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, + 180 + ], + "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": true, + "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-diagrams/index.ts b/ng2-components/ng2-activiti-diagrams/index.ts new file mode 100644 index 0000000000..121a0c86bf --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/index.ts @@ -0,0 +1,56 @@ +/*! + * @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 { NgModule, ModuleWithProviders } from '@angular/core'; +import { CoreModule } from 'ng2-alfresco-core'; + +import { DIAGRAM_DIRECTIVES, DIAGRAM_PROVIDERS } from './src/components/index'; + +import { RAPHAEL_DIRECTIVES } from './src/components/raphael/index'; +import { RAPHAEL_PROVIDERS } from './src/components/raphael/index'; + +// primitives +export * from './src/components/index'; +export * from './src/components/raphael/index'; + +@NgModule({ + imports: [ + CoreModule + ], + declarations: [ + ...DIAGRAM_DIRECTIVES, + ...RAPHAEL_DIRECTIVES + ], + providers: [ + ...DIAGRAM_PROVIDERS, + ...RAPHAEL_PROVIDERS + ], + exports: [ + ...DIAGRAM_DIRECTIVES + ] +}) +export class DiagramsModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: DiagramsModule, + providers: [ + ...DIAGRAM_PROVIDERS, + ...RAPHAEL_PROVIDERS + ] + }; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/karma-test-shim.js b/ng2-components/ng2-activiti-diagrams/karma-test-shim.js new file mode 100644 index 0000000000..47e3d71f55 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/karma-test-shim.js @@ -0,0 +1,104 @@ +// Tun on full stack traces in errors to help debugging +Error.stackTraceLimit = Infinity; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +__karma__.loaded = function() {}; + +var builtPath = '/base/dist/'; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +var paths = { + // paths serve as alias + 'npm:': 'base/node_modules/' +}; + +var map = { + 'app': 'base/dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // testing + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-activiti-diagrams': 'npm:ng2-activiti-diagrams/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist' +}; + +var packages = { + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { defaultExtension: 'js' }, + 'ng2-translate': { defaultExtension: 'js' }, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-activiti-diagrams': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'} +}; + +var config = { + paths: paths, + map: map, + packages: packages +}; + +System.config(config); + +System.import('@angular/core/testing') + .then(initTestBed) + .then(initTesting); + +function initTestBed(){ + return Promise.all([ + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') + ]) + .then(function (providers) { + var coreTesting = providers[0]; + var browserTesting = providers[1]; + + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); + }) +} + +// Import all spec files and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { + return System.import(moduleName); + }) + ) + .then(__karma__.start, __karma__.error); +} diff --git a/ng2-components/ng2-activiti-diagrams/karma.conf.js b/ng2-components/ng2-activiti-diagrams/karma.conf.js new file mode 100644 index 0000000000..2806957c37 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/karma.conf.js @@ -0,0 +1,121 @@ +'use strict'; + +module.exports = function (config) { + var configuration = { + basePath: '.', + + frameworks: ['jasmine-ajax', 'jasmine'], + + files: [ + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', + + // Polyfills + 'node_modules/core-js/client/shim.js', + 'node_modules/reflect-metadata/Reflect.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false}, + + // Paths loaded via module imports: + // Angular itself + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, + + 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + 'node_modules/raphael/raphael.min.js', + 'assets/Polyline.js', + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, + + 'karma-test-shim.js', + + // 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}, + + // ng2-components + { pattern: 'node_modules/ng2-alfresco-core/dist/**/*.*', included: false, served: true, watched: false }, + + // 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|index|*mock|*model).js': 'coverage' + }, + + coverageReporter: { + includeAllSources: true, + 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-diagrams/package.json b/ng2-components/ng2-activiti-diagrams/package.json new file mode 100644 index 0000000000..28bbbdfa74 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/package.json @@ -0,0 +1,102 @@ +{ + "name": "ng2-activiti-diagrams", + "description": "Activiti Angular2 Diagrams Component", + "version": "0.4.0", + "author": "Alfresco Software, Ltd.", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "scripts": { + "clean": "npm install rimraf && rimraf dist node_modules typings", + "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" + }, + "contributors": [ + { + "name": "Maurizio Vitale", + "email": "maurizio.vitale84@gmail.com" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/Alfresco/alfresco-ng2-components.git" + }, + "bugs": { + "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" + }, + "dependencies": { + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "raphael": "^2.2.6", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0" + }, + "devDependencies": { + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", + "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-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", + "remap-istanbul": "0.6.3", + "rimraf": "2.5.2", + "traceur": "0.0.91", + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" + }, + "keywords": [ + "tag", + "alfresco-analytics", + "alfresco-diagrams" + ], + "license-check-config": { + "src": [ + "./dist/**/*.js" + ], + "path": "assets/license_header.txt", + "blocking": true, + "logInfo": false, + "logError": true + }, + "license": "Apache-2.0" +} diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramActivities.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramActivities.mock.ts new file mode 100644 index 0000000000..ec7a3b31aa --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramActivities.mock.ts @@ -0,0 +1,166 @@ +/*! + * @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 let userTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake User task', + type: 'UserTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}] +}; + +export let manualTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Manual task', + type: 'ManualTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}] +}; + +export let serviceTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Service task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}] +}; + +export let receiveTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Receive task', + type: 'ReceiveTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}] +}; + +export let scriptTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Script task', + type: 'ScriptTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}] +}; + +export let businessRuleTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake BusinessRule task', + type: 'BusinessRuleTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}] +}; + +export let mailTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Mail task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}], + taskType: 'mail' +}; + +export let camelTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Camel task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}], + taskType: 'camel' +}; + +export let restCallTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Rest Call task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}], + taskType: 'rest_call' +}; + +export let muleTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Mule task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}], + taskType: 'mule' +}; + +export let alfrescoPublishTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Alfresco Publish task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}], + taskType: 'alfresco_publish' +}; + +export let googleDrivePublishTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Google Drive Publish task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}], + taskType: 'google_drive_publish' +}; + +export let boxPublishTask = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + name: 'Fake Box Publish task', + type: 'ServiceTask', + width: 82, + height: 67, + x: 75.56890135999998, + y: 30, + properties: [{}], + taskType: 'box_publish' +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramBoundary.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramBoundary.mock.ts new file mode 100644 index 0000000000..daaf33a467 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramBoundary.mock.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. + */ + +export let boundaryTimeEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'BoundaryEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'timer'} +}; + +export let boundaryErrorEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'BoundaryEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'error'} +}; + +export let boundarySignalEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'BoundaryEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'signal'} +}; + +export let boundaryMessageEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'BoundaryEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'message'} +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramEvents.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramEvents.mock.ts new file mode 100644 index 0000000000..404208d178 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramEvents.mock.ts @@ -0,0 +1,91 @@ +/*! + * @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 let startEvent = { + id: 'startEvent1', + type: 'StartEvent', + width: 30, + height: 30, + x: 15, + y: 48.5, + properties: [{}] +}; + +export let startTimeEvent = { + id: 'startEvent1', + type: 'StartEvent', + width: 30, + height: 30, + x: 15, + y: 48.5, + eventDefinition: {type: 'timer'}, + properties: [{}] +}; + +export let startSignalEvent = { + id: 'startEvent1', + type: 'StartEvent', + width: 30, + height: 30, + x: 15, + y: 48.5, + eventDefinition: {type: 'signal'}, + properties: [{}] +}; + +export let startMessageEvent = { + id: 'startEvent1', + type: 'StartEvent', + width: 30, + height: 30, + x: 15, + y: 48.5, + eventDefinition: {type: 'message'}, + properties: [{}] +}; + +export let startErrorEvent = { + id: 'startEvent1', + type: 'StartEvent', + width: 30, + height: 30, + x: 15, + y: 48.5, + eventDefinition: {type: 'error'}, + properties: [{}] +}; + +export let endEvent = { + id: 'sid-CED2A8DB-47E2-4057-A7B8-3ABBE5CE795E', + type: 'EndEvent', + width: 28, + height: 28, + x: 15, + y: 48.5, + properties: [{}] +}; + +export let endErrorEvent = { + id: 'sid-CED2A8DB-47E2-4057-A7B8-3ABBE5CE795E', + type: 'EndEvent', + width: 28, + height: 28, + x: 15, + y: 48.5, + eventDefinition: {type: 'error'}, + properties: [{}] +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramFlows.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramFlows.mock.ts new file mode 100644 index 0000000000..68a3b32b68 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramFlows.mock.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. + */ + +export let flow = { + id: 'sid-5BA99724-A3BD-4F8E-B69F-222F9FF66335', + sourceRef: 'startEvent1', + targetRef: 'sid-811B9223-E72E-4991-AAA5-4E1A01095D08', + type: 'sequenceFlow', + waypoints: [{x: 165, y: 122}, {x: 210, y: 122}] +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramGateways.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramGateways.mock.ts new file mode 100644 index 0000000000..85163c3d4f --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramGateways.mock.ts @@ -0,0 +1,56 @@ +/*! + * @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 let exclusiveGatway = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'ExclusiveGateway', + width: 40, + height: 40, + x: 40, + y: 30, + properties: [{}] +}; + +export let inclusiveGatway = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'InclusiveGateway', + width: 40, + height: 40, + x: 40, + y: 30, + properties: [{}] +}; + +export let parallelGatway = { + id: 'sid-14EE23CE-0731-4E23-80F3-C557DA2A0CFC', + type: 'ParallelGateway', + width: 40, + height: 40, + x: 40, + y: 30, + properties: [{}] +}; + +export let eventGatway = { + id: 'sid-14EE23CE-0731-4E23-80F3-C557DA2A0CFC', + type: 'EventGateway', + width: 40, + height: 40, + x: 40, + y: 30, + properties: [{}] +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramIntermediate.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramIntermediate.mock.ts new file mode 100644 index 0000000000..600156fd01 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramIntermediate.mock.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. + */ + +export let intermediateCatchingTimeEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'IntermediateCatchEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'timer'} +}; + +export let intermediateCatchingErrorEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'IntermediateCatchEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'error'} +}; + +export let intermediateCatchingSignalEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'IntermediateCatchEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'signal'} +}; + +export let intermediateCatchingMessageEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'IntermediateCatchEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'message'} +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramStructural.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramStructural.mock.ts new file mode 100644 index 0000000000..4dace0d7e3 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramStructural.mock.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. + */ + +export let subProcess = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'SubProcess', + width: 300, + height: 200, + x: 40, + y: 30, + properties: [{}] +}; + +export let eventSubProcess = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'EventSubProcess', + width: 300, + height: 200, + x: 40, + y: 30, + properties: [{}] +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramSwimlanes.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramSwimlanes.mock.ts new file mode 100644 index 0000000000..e9d41c2131 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramSwimlanes.mock.ts @@ -0,0 +1,44 @@ +/*! + * @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 let pool = { + id: 'sid-0FF10DA3-E2BD-4E6A-9013-6D66FC8A4716', + name: 'Activiti', + width: 600, + height: 500, + x: 60, + y: 45, + properties: [{}] +}; + +export let poolLanes = { + id: 'sid-0FF10DA3-E2BD-4E6A-9013-6D66FC8A4716', + name: 'Activiti', + width: 600, + height: 500, + x: 60, + y: 45, + lanes: [{ + id: 'sid-332204AB-D0F8-44CD-87B3-BF9DF59FF8AB', + name: 'Beckend', + width: 570, + height: 250, + x: 90, + y: 45 + }], + properties: [{}] +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/assets/diagramThrow.mock.ts b/ng2-components/ng2-activiti-diagrams/src/assets/diagramThrow.mock.ts new file mode 100644 index 0000000000..f854de07f1 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/assets/diagramThrow.mock.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. + */ + +export let throwTimeEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'ThrowEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'timer'} +}; + +export let throwErrorEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'ThrowEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'error'} +}; + +export let throwSignalEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'ThrowEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'signal'} +}; + +export let throwMessageEvent = { + id: 'sid-C05B7CB7-1CFD-4AE4-9E01-C2C91E35E5A7', + type: 'ThrowEvent', + width: 31, + height: 31, + x: 40, + y: 30, + properties: [{}], + eventDefinition: {type: 'message'} +}; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-alfresco-publish-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-alfresco-publish-task.component.html new file mode 100644 index 0000000000..5a7c236f1f --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-alfresco-publish-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-alfresco-publish-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-alfresco-publish-task.component.ts new file mode 100644 index 0000000000..3b890b22b0 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-alfresco-publish-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-alfresco-publish-task', + templateUrl: './diagram-alfresco-publish-task.component.html' +}) +export class DiagramAlfrescoPublishTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-box-publish-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-box-publish-task.component.html new file mode 100644 index 0000000000..6b60f0a5a5 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-box-publish-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-box-publish-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-box-publish-task.component.ts new file mode 100644 index 0000000000..869169446e --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-box-publish-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-box-publish-task', + templateUrl: './diagram-box-publish-task.component.html' +}) +export class DiagramBoxPublishTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-business-rule-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-business-rule-task.component.html new file mode 100644 index 0000000000..c336058d68 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-business-rule-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-business-rule-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-business-rule-task.component.ts new file mode 100644 index 0000000000..b88f7e79b4 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-business-rule-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-business-rule-task', + templateUrl: './diagram-business-rule-task.component.html' +}) +export class DiagramBusinessRuleTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-camel-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-camel-task.component.html new file mode 100644 index 0000000000..da76d3bb68 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-camel-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-camel-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-camel-task.component.ts new file mode 100644 index 0000000000..98e0e38885 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-camel-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-camel-task', + templateUrl: './diagram-camel-task.component.html' +}) +export class DiagramCamelTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-container-service-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-container-service-task.component.html new file mode 100644 index 0000000000..ffca2963c4 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-container-service-task.component.html @@ -0,0 +1,26 @@ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-container-service-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-container-service-task.component.ts new file mode 100644 index 0000000000..d838f55ca6 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-container-service-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-container-service-task', + templateUrl: './diagram-container-service-task.component.html' +}) +export class DiagramContainerServiceTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-google-drive-publish-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-google-drive-publish-task.component.html new file mode 100644 index 0000000000..953f75733a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-google-drive-publish-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-google-drive-publish-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-google-drive-publish-task.component.ts new file mode 100644 index 0000000000..1cb11f9e62 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-google-drive-publish-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-google-drive-publish-task', + templateUrl: './diagram-google-drive-publish-task.component.html' +}) +export class DiagramGoogleDrivePublishTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-manual-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-manual-task.component.html new file mode 100644 index 0000000000..35c1d11660 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-manual-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-manual-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-manual-task.component.ts new file mode 100644 index 0000000000..b4bd42f15f --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-manual-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-manual-task', + templateUrl: './diagram-manual-task.component.html' +}) +export class DiagramManualTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-mule-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-mule-task.component.html new file mode 100644 index 0000000000..882a84ff19 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-mule-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-mule-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-mule-task.component.ts new file mode 100644 index 0000000000..73b015cbda --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-mule-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-mule-task', + templateUrl: './diagram-mule-task.component.html' +}) +export class DiagramMuleTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-receive-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-receive-task.component.html new file mode 100644 index 0000000000..d2718f6daf --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-receive-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-receive-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-receive-task.component.ts new file mode 100644 index 0000000000..625ddd7c49 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-receive-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-receive-task', + templateUrl: './diagram-receive-task.component.html' +}) +export class DiagramReceiveTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-rest-call-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-rest-call-task.component.html new file mode 100644 index 0000000000..e7e27ad5bd --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-rest-call-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-rest-call-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-rest-call-task.component.ts new file mode 100644 index 0000000000..6e41f58d75 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-rest-call-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-rest-call-task', + templateUrl: './diagram-rest-call-task.component.html' +}) +export class DiagramRestCallTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-script-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-script-task.component.html new file mode 100644 index 0000000000..3636798f8d --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-script-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-script-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-script-task.component.ts new file mode 100644 index 0000000000..65469f9d7c --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-script-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-script-task', + templateUrl: './diagram-script-task.component.html' +}) +export class DiagramScriptTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-send-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-send-task.component.html new file mode 100644 index 0000000000..1d62444634 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-send-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-send-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-send-task.component.ts new file mode 100644 index 0000000000..90f106dd80 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-send-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-send-task', + templateUrl: './diagram-send-task.component.html' +}) +export class DiagramSendTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-service-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-service-task.component.html new file mode 100644 index 0000000000..06e28b4019 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-service-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-service-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-service-task.component.ts new file mode 100644 index 0000000000..8498b342a7 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-service-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-service-task', + templateUrl: './diagram-service-task.component.html' +}) +export class DiagramServiceTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-task.component.html new file mode 100644 index 0000000000..fe7f52ec74 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-task.component.html @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-task.component.ts new file mode 100644 index 0000000000..11adfe077f --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-task.component.ts @@ -0,0 +1,49 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-task', + templateUrl: './diagram-task.component.html' +}) +export class DiagramTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + rectLeftCorner: any; + textPosition: any; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: '', radius: 4}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.rectLeftCorner = {x: this.data.x, y: this.data.y}; + this.textPosition = {x: this.data.x + ( this.data.width / 2 ), y: this.data.y + ( this.data.height / 2 )}; + + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.ACTIVITY_STROKE_COLOR); + this.options.strokeWidth = this.diagramColorService.getBpmnStrokeWidth(this.data); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-user-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-user-task.component.html new file mode 100644 index 0000000000..cb4c4a602a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-user-task.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-user-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-user-task.component.ts new file mode 100644 index 0000000000..9507bc0b4b --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/diagram-user-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-user-task', + templateUrl: './diagram-user-task.component.html' +}) +export class DiagramUserTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/activities/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/activities/index.ts new file mode 100644 index 0000000000..3bafb65341 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/activities/index.ts @@ -0,0 +1,67 @@ +/*! + * @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 { DiagramContainerServiceTaskComponent } from './diagram-container-service-task.component'; +import { DiagramTaskComponent } from './diagram-task.component'; +import { DiagramServiceTaskComponent } from './diagram-service-task.component'; +import { DiagramSendTaskComponent } from './diagram-send-task.component'; +import { DiagramUserTaskComponent } from './diagram-user-task.component'; +import { DiagramManualTaskComponent } from './diagram-manual-task.component'; +import { DiagramCamelTaskComponent } from './diagram-camel-task.component'; +import { DiagramMuleTaskComponent } from './diagram-mule-task.component'; +import { DiagramAlfrescoPublishTaskComponent } from './diagram-alfresco-publish-task.component'; +import { DiagramRestCallTaskComponent } from './diagram-rest-call-task.component'; +import { DiagramGoogleDrivePublishTaskComponent } from './diagram-google-drive-publish-task.component'; +import { DiagramBoxPublishTaskComponent } from './diagram-box-publish-task.component'; +import { DiagramReceiveTaskComponent } from './diagram-receive-task.component'; +import { DiagramScriptTaskComponent } from './diagram-script-task.component'; +import { DiagramBusinessRuleTaskComponent } from './diagram-business-rule-task.component'; + +// primitives +export * from './diagram-container-service-task.component'; +export * from './diagram-task.component'; +export * from './diagram-service-task.component'; +export * from './diagram-send-task.component'; +export * from './diagram-user-task.component'; +export * from './diagram-manual-task.component'; +export * from './diagram-camel-task.component'; +export * from './diagram-mule-task.component'; +export * from './diagram-alfresco-publish-task.component'; +export * from './diagram-rest-call-task.component'; +export * from './diagram-google-drive-publish-task.component'; +export * from './diagram-box-publish-task.component'; +export * from './diagram-receive-task.component'; +export * from './diagram-script-task.component'; +export * from './diagram-business-rule-task.component'; + +export const DIAGRAM_ACTIVITIES_DIRECTIVES: any[] = [ + DiagramContainerServiceTaskComponent, + DiagramTaskComponent, + DiagramServiceTaskComponent, + DiagramSendTaskComponent, + DiagramUserTaskComponent, + DiagramManualTaskComponent, + DiagramCamelTaskComponent, + DiagramMuleTaskComponent, + DiagramAlfrescoPublishTaskComponent, + DiagramRestCallTaskComponent, + DiagramGoogleDrivePublishTaskComponent, + DiagramBoxPublishTaskComponent, + DiagramReceiveTaskComponent, + DiagramScriptTaskComponent, + DiagramBusinessRuleTaskComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-boundary-event.component.html b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-boundary-event.component.html new file mode 100644 index 0000000000..9607de4538 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-boundary-event.component.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-boundary-event.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-boundary-event.component.ts new file mode 100644 index 0000000000..b89736cdc0 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-boundary-event.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-boundary-event', + templateUrl: './diagram-boundary-event.component.html' +}) +export class DiagramBoundaryEventComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 1}; + + signalFillColor: string; + + circleRadiusInner: number; + circleRadiusOuter: number; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x + (this.data.width / 2); + this.center.y = this.data.y + (this.data.height / 2); + + this.circleRadiusInner = 12; + this.circleRadiusOuter = 15; + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + + this.signalFillColor = 'none'; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-throw-event.component.html b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-throw-event.component.html new file mode 100644 index 0000000000..b4dddf5c31 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-throw-event.component.html @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-throw-event.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-throw-event.component.ts new file mode 100644 index 0000000000..d87691f67c --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/diagram-throw-event.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-throw-event', + templateUrl: './diagram-throw-event.component.html' +}) +export class DiagramThrowEventComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 1}; + + signalFillColor: string; + + circleRadiusInner: number; + circleRadiusOuter: number; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x + (this.data.width / 2); + this.center.y = this.data.y + (this.data.height / 2); + + this.circleRadiusInner = 12; + this.circleRadiusOuter = 15; + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + + this.signalFillColor = 'black'; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/index.ts new file mode 100644 index 0000000000..248bf142cc --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/boundary-events/index.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. + */ + +import { DiagramBoundaryEventComponent } from './diagram-boundary-event.component'; +import { DiagramThrowEventComponent } from './diagram-throw-event.component'; + +// primitives +export * from './diagram-boundary-event.component'; +export * from './diagram-throw-event.component'; + +export const DIAGRAM_BOUNDARY_EVENTS_DIRECTIVES: any[] = [ + DiagramBoundaryEventComponent, + DiagramThrowEventComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/diagram-sequence-flow.component.html b/ng2-components/ng2-activiti-diagrams/src/components/diagram-sequence-flow.component.html new file mode 100644 index 0000000000..985c72c443 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/diagram-sequence-flow.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/diagram-sequence-flow.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/diagram-sequence-flow.component.ts new file mode 100644 index 0000000000..aa89eecbf6 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/diagram-sequence-flow.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-sequence-flow', + templateUrl: './diagram-sequence-flow.component.html' +}) +export class DiagramSequenceFlowComponent { + @Input() + flow: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + console.log(this.elementRef); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.html b/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.html new file mode 100644 index 0000000000..9cf62cc2d4 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.html @@ -0,0 +1,67 @@ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
diff --git a/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.spec.ts b/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.spec.ts new file mode 100644 index 0000000000..67a9b0ebcc --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.spec.ts @@ -0,0 +1,1252 @@ +/*! + * @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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { + CoreModule +} from 'ng2-alfresco-core'; + +import { DIAGRAM_DIRECTIVES, DIAGRAM_PROVIDERS } from './index'; +import { RAPHAEL_DIRECTIVES, RAPHAEL_PROVIDERS } from './raphael/index'; +import { DiagramComponent } from './index'; +import { DebugElement } from '@angular/core'; +import * as diagramsEventsMock from '../assets/diagramEvents.mock'; +import * as diagramsActivitiesMock from '../assets/diagramActivities.mock'; +import * as diagramsGatewaysMock from '../assets/diagramGateways.mock'; +import * as intermediateCatchingMock from '../assets/diagramIntermediate.mock'; +import * as boundaryEventMock from '../assets/diagramBoundary.mock'; +import * as throwEventMock from '../assets/diagramThrow.mock'; +import * as structuralMock from '../assets/diagramStructural.mock'; +import * as swimLanesMock from '../assets/diagramSwimlanes.mock'; +import * as flowsMock from '../assets/diagramFlows.mock'; + +declare let jasmine: any; + +describe('Test ng2-activiti-diagrams ', () => { + + let component: any; + let fixture: ComponentFixture; + let debug: DebugElement; + let element: HTMLElement; + + let componentHandler: any; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule + ], + declarations: [ + ...DIAGRAM_DIRECTIVES, + ...RAPHAEL_DIRECTIVES + ], + providers: [ + ...DIAGRAM_PROVIDERS, + ...RAPHAEL_PROVIDERS + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DiagramComponent); + component = fixture.componentInstance; + debug = fixture.debugElement; + element = fixture.nativeElement; + fixture.detectChanges(); + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + }); + + describe('Diagrams component Events: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Start Event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let event: any = element.querySelector('diagram-start-event > diagram-event > raphael-circle'); + expect(event).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsEventsMock.startEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Start Timer Event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).toBeDefined(); + let event: any = element.querySelector('diagram-start-event > diagram-event > raphael-circle'); + expect(event).not.toBeNull(); + + let iconEvent: any = element.querySelector('diagram-start-event > diagram-event >' + + ' diagram-container-icon-event > div > div > diagram-icon-timer > raphael-icon-timer'); + expect(iconEvent).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsEventsMock.startTimeEvent]}; + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Start Signal Event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).toBeDefined(); + let event: any = element.querySelector('diagram-start-event > diagram-event > raphael-circle'); + expect(event).not.toBeNull(); + + let iconEvent: any = element.querySelector('diagram-start-event > diagram-event >' + + ' diagram-container-icon-event > div > div > diagram-icon-signal > raphael-icon-signal'); + expect(iconEvent).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsEventsMock.startSignalEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Start Message Event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).toBeDefined(); + let event: any = element.querySelector('diagram-start-event > diagram-event > raphael-circle'); + expect(event).not.toBeNull(); + + let iconEvent: any = element.querySelector('diagram-start-event > diagram-event >' + + ' diagram-container-icon-event > div > div > diagram-icon-message > raphael-icon-message'); + expect(iconEvent).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsEventsMock.startMessageEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Start Error Event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).toBeDefined(); + let event: any = element.querySelector('diagram-start-event > diagram-event > raphael-circle'); + expect(event).not.toBeNull(); + + let iconEvent: any = element.querySelector('diagram-start-event > diagram-event >' + + ' diagram-container-icon-event > div > div > diagram-icon-error > raphael-icon-error'); + expect(iconEvent).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsEventsMock.startErrorEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the End Event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).toBeDefined(); + let event: any = element.querySelector('diagram-end-event > diagram-event > raphael-circle'); + expect(event).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsEventsMock.endEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the End Error Event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).toBeDefined(); + let event: any = element.querySelector('diagram-end-event > diagram-event > raphael-circle'); + expect(event).not.toBeNull(); + + let iconEvent: any = element.querySelector('diagram-end-event > diagram-event >' + + ' diagram-container-icon-event > div > div > diagram-icon-error > raphael-icon-error'); + expect(iconEvent).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsEventsMock.endErrorEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); + + describe('Diagrams component Activities: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the User Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-user-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-user-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake User task'); + + let iconTask: any = element.querySelector('diagram-user-task > diagram-icon-user-task > raphael-icon-user'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.userTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Manual Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-manual-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-manual-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Manual task'); + + let iconTask: any = element.querySelector('diagram-manual-task > diagram-icon-manual-task > raphael-icon-manual'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.manualTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Service Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-service-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-service-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Service task'); + + let iconTask: any = element.querySelector('diagram-service-task > diagram-icon-service-task > raphael-icon-service'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.serviceTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Service Mail Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(true).toBe(true); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.mailTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Service Camel Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-camel-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-camel-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Camel task'); + + let iconTask: any = element.querySelector('diagram-camel-task > diagram-icon-camel-task > raphael-icon-camel'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.camelTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Service Mule Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-mule-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-mule-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Mule task'); + + let iconTask: any = element.querySelector('diagram-mule-task > diagram-icon-mule-task > raphael-icon-mule'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.muleTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Service Alfresco Publish Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-alfresco-publish-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-alfresco-publish-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Alfresco Publish task'); + + let iconTask: any = element.querySelector('diagram-alfresco-publish-task > diagram-icon-alfresco-publish-task >' + + ' raphael-icon-alfresco-publish'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.alfrescoPublishTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Service Google Drive Publish Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-google-drive-publish-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-google-drive-publish-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Google Drive Publish task'); + + let iconTask: any = element.querySelector('diagram-google-drive-publish-task >' + + ' diagram-icon-google-drive-publish-task > raphael-icon-google-drive-publish'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.googleDrivePublishTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Rest Call Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-rest-call-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-rest-call-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Rest Call task'); + + let iconTask: any = element.querySelector('diagram-rest-call-task > diagram-icon-rest-call-task >' + + ' raphael-icon-rest-call'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.restCallTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Service Box Publish Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-box-publish-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-box-publish-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Box Publish task'); + + let iconTask: any = element.querySelector('diagram-box-publish-task >' + + ' diagram-icon-box-publish-task > raphael-icon-box-publish'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.boxPublishTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Receive Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-receive-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-receive-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Receive task'); + + let iconTask: any = element.querySelector('diagram-receive-task > diagram-icon-receive-task > raphael-icon-receive'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.receiveTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Script Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-script-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-script-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake Script task'); + + let iconTask: any = element.querySelector('diagram-script-task > diagram-icon-script-task > raphael-icon-script'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.scriptTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Business Rule Task', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let task: any = element.querySelector('diagram-business-rule-task > diagram-task > raphael-rect'); + expect(task).not.toBeNull(); + + let taskText: any = element.querySelector('diagram-business-rule-task > diagram-task > raphael-text'); + expect(taskText).not.toBeNull(); + expect(taskText.attributes[1].value).toEqual('Fake BusinessRule task'); + + let iconTask: any = element.querySelector('diagram-business-rule-task > diagram-icon-business-rule-task > raphael-icon-business-rule'); + expect(iconTask).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsActivitiesMock.businessRuleTask]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + }); + + describe('Diagrams component Gateways: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Exclusive Gateway', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-exclusive-gateway > diagram-gateway > raphael-rhombus'); + expect(shape).not.toBeNull(); + + let shape1: any = element.querySelector('diagram-exclusive-gateway > raphael-cross'); + expect(shape1).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsGatewaysMock.exclusiveGatway]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Inclusive Gateway', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-inclusive-gateway > diagram-gateway > raphael-rhombus'); + expect(shape).not.toBeNull(); + + let shape1: any = element.querySelector('diagram-inclusive-gateway > raphael-circle'); + expect(shape1).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsGatewaysMock.inclusiveGatway]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Parallel Gateway', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-parallel-gateway > diagram-gateway > raphael-rhombus'); + expect(shape).not.toBeNull(); + + let shape1: any = element.querySelector('diagram-parallel-gateway > raphael-plus'); + expect(shape1).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsGatewaysMock.parallelGatway]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Event Gateway', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-event-gateway > diagram-gateway > raphael-rhombus'); + expect(shape).not.toBeNull(); + + let shape1: any = element.querySelector('diagram-event-gateway'); + expect(shape1).not.toBeNull(); + expect(shape1.children.length).toBe(4); + + let outerCircle = shape1.children[1]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape1.children[2]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let shape2: any = element.querySelector('diagram-event-gateway > raphael-pentagon'); + expect(shape2).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [diagramsGatewaysMock.eventGatway]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); + + describe('Diagrams component Intermediate Catching events: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Intermediate catching time event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-intermediate-catching-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-intermediate-catching-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-timer'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [intermediateCatchingMock.intermediateCatchingTimeEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Intermediate catching error event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-intermediate-catching-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-intermediate-catching-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-error'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [intermediateCatchingMock.intermediateCatchingErrorEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Intermediate catching signal event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-intermediate-catching-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-intermediate-catching-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-signal'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [intermediateCatchingMock.intermediateCatchingSignalEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Intermediate catching signal message', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-intermediate-catching-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-intermediate-catching-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-message'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [intermediateCatchingMock.intermediateCatchingMessageEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); + + describe('Diagrams component Boundary events: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Boundary time event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-boundary-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-boundary-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-timer'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [boundaryEventMock.boundaryTimeEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Boundary error event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-boundary-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-boundary-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-error'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [boundaryEventMock.boundaryErrorEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Boundary signal event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-boundary-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-boundary-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-signal'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [boundaryEventMock.boundarySignalEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Boundary signal message', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-boundary-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-boundary-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-message'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [boundaryEventMock.boundaryMessageEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Boundary signal message', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-boundary-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-boundary-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-message'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [boundaryEventMock.boundaryMessageEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); + + describe('Diagrams component Throw events: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Throw time event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-throw-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-throw-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-timer'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [throwEventMock.throwTimeEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Throw error event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-throw-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-throw-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-error'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [throwEventMock.throwErrorEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Throw signal event', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-throw-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-throw-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-signal'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [throwEventMock.throwSignalEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Throw signal message', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-throw-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-throw-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-message'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [throwEventMock.throwMessageEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Throw signal message', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-throw-event'); + expect(shape).not.toBeNull(); + expect(shape.children.length).toBe(3); + + let outerCircle = shape.children[0]; + expect(outerCircle.localName).toEqual('raphael-circle'); + + let innerCircle = shape.children[1]; + expect(innerCircle.localName).toEqual('raphael-circle'); + + let iconShape: any = element.querySelector('diagram-throw-event > diagram-container-icon-event >' + + ' div > div > diagram-icon-message'); + expect(iconShape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [throwEventMock.throwMessageEvent]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); + + describe('Diagrams component Structural: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Subprocess', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-subprocess > raphael-rect'); + expect(shape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [structuralMock.subProcess]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Event Subprocess', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-event-subprocess > raphael-rect'); + expect(shape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {elements: [structuralMock.eventSubProcess]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); + + describe('Diagrams component Swim lane: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the Pool', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-pool > raphael-rect'); + expect(shape).not.toBeNull(); + + let shapeText: any = element.querySelector('diagram-pool > raphael-text'); + expect(shapeText).not.toBeNull(); + expect(shapeText.attributes[2].value).toEqual('Activiti'); + }); + }); + component.ngOnChanges(); + let resp = {pools: [swimLanesMock.pool]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + + it('Should render the Pool with Lanes', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shapeLane: any = element.querySelector('diagram-lanes > div > div > diagram-lane'); + expect(shapeLane).not.toBeNull(); + + let shapeRect: any = element.querySelector('diagram-lanes > div > div > diagram-lane > raphael-rect'); + expect(shapeRect).not.toBeNull(); + + let shapeText: any = element.querySelector('diagram-lanes > div > div > diagram-lane > raphael-text'); + expect(shapeText).not.toBeNull(); + expect(shapeText.attributes[2].value).toEqual('Beckend'); + }); + }); + component.ngOnChanges(); + let resp = {pools: [swimLanesMock.poolLanes]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); + + describe('Diagrams component Flows: ', () => { + beforeEach(() => { + jasmine.Ajax.install(); + component.processDefinitionId = 'fakeprocess:24:38399'; + component.metricPercentages = {startEvent: 0}; + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should render the flow', async(() => { + component.onSuccess.subscribe((res) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(res).not.toBeNull(); + let shape: any = element.querySelector('diagram-sequence-flow > raphael-flow-arrow'); + expect(shape).not.toBeNull(); + }); + }); + component.ngOnChanges(); + let resp = {flows: [flowsMock.flow]}; + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: resp + }); + })); + }); +}); diff --git a/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.ts new file mode 100644 index 0000000000..30d87ab5f2 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/diagram.component.ts @@ -0,0 +1,88 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { DiagramsService } from '../services/diagrams.service'; +import { DiagramColorService } from '../services/diagram-color.service'; +import { RaphaelService } from './raphael/raphael.service'; + +@Component({ + moduleId: module.id, + selector: 'activiti-diagram', + templateUrl: './diagram.component.html' +}) +export class DiagramComponent { + @Input() + processDefinitionId: any; + + @Input() + metricPercentages: any; + + @Input() + width: number = 1000; + + @Input() + height: number = 500; + + @Output() + onSuccess = new EventEmitter(); + + @Output() + onError = new EventEmitter(); + + private diagram: any; + private elementRef: ElementRef; + + constructor(elementRef: ElementRef, + private translate: AlfrescoTranslationService, + private diagramColorService: DiagramColorService, + private raphaelService: RaphaelService, + private diagramsService: DiagramsService) { + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src'); + } + this.elementRef = elementRef; + } + + ngOnInit() { + this.raphaelService.setting(this.width, this.height); + } + + ngOnChanges(changes: SimpleChanges) { + this.reset(); + this.diagramColorService.setTotalColors(this.metricPercentages); + this.getProcessDefinitionModel(this.processDefinitionId); + } + + getProcessDefinitionModel(processDefinitionId: string) { + this.diagramsService.getProcessDefinitionModel(processDefinitionId).subscribe( + (res: any) => { + this.diagram = res; + this.onSuccess.emit(res); + }, + (err: any) => { + this.onError.emit(err); + console.log(err); + } + ); + } + + reset() { + this.raphaelService.reset(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-end-event.component.html b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-end-event.component.html new file mode 100644 index 0000000000..ae975d5b9f --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-end-event.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-end-event.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-end-event.component.ts new file mode 100644 index 0000000000..c7b8a9287d --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-end-event.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-end-event', + templateUrl: './diagram-end-event.component.html' +}) +export class DiagramEndEventComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: '', radius: ''}; + iconFillColor: any; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + console.log(this.elementRef); + + this.options.radius = 14; + this.options.strokeWidth = 4; + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + + this.iconFillColor = 'black'; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-event.component.html b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-event.component.html new file mode 100644 index 0000000000..e85a6aee8d --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-event.component.html @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-event.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-event.component.ts new file mode 100644 index 0000000000..8bf1ebd557 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-event.component.ts @@ -0,0 +1,49 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-event', + templateUrl: './diagram-event.component.html' +}) +export class DiagramEventComponent { + @Input() + data: any; + + @Input() + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: '', radius: ''}; + + @Input() + iconFillColor: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + console.log(this.elementRef); + this.center.x = this.data.x + (this.data.width / 2); + this.center.y = this.data.y + (this.data.height / 2); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-start-event.component.html b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-start-event.component.html new file mode 100644 index 0000000000..ae975d5b9f --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-start-event.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-start-event.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-start-event.component.ts new file mode 100644 index 0000000000..83d0369894 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/events/diagram-start-event.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-start-event', + templateUrl: './diagram-start-event.component.html' +}) +export class DiagramStartEventComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: '', radius: ''}; + iconFillColor: any; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + console.log(this.elementRef); + + this.options.radius = 15; + this.options.strokeWidth = 1; + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + + this.iconFillColor = 'none'; + } +} diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoPipeTranslate.service.ts b/ng2-components/ng2-activiti-diagrams/src/components/events/index.ts similarity index 54% rename from ng2-components/ng2-alfresco-core/src/services/AlfrescoPipeTranslate.service.ts rename to ng2-components/ng2-activiti-diagrams/src/components/events/index.ts index 12ba8b9b7f..fe24b0eb39 100644 --- a/ng2-components/ng2-alfresco-core/src/services/AlfrescoPipeTranslate.service.ts +++ b/ng2-components/ng2-activiti-diagrams/src/components/events/index.ts @@ -15,18 +15,17 @@ * limitations under the License. */ -import { Injectable, ChangeDetectorRef, Pipe } from '@angular/core'; -import { TranslatePipe } from 'ng2-translate/ng2-translate'; -import { AlfrescoTranslationService } from './AlfrescoTranslation.service'; +import { DiagramEventComponent } from './diagram-event.component'; +import { DiagramStartEventComponent } from './diagram-start-event.component'; +import { DiagramEndEventComponent } from './diagram-end-event.component'; -@Injectable() -@Pipe({ - name: 'translate', - pure: false // required to update the value when the promise is resolved -}) -export class AlfrescoPipeTranslate extends TranslatePipe { +// primitives +export * from './diagram-event.component'; +export * from './diagram-start-event.component'; +export * from './diagram-end-event.component'; - constructor(translate: AlfrescoTranslationService, _ref: ChangeDetectorRef) { - super(translate, _ref); - } -} +export const DIAGRAM_EVENTS_DIRECTIVES: any[] = [ + DiagramEventComponent, + DiagramStartEventComponent, + DiagramEndEventComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-event-gateway.component.html b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-event-gateway.component.html new file mode 100644 index 0000000000..bf3e80e11e --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-event-gateway.component.html @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-event-gateway.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-event-gateway.component.ts new file mode 100644 index 0000000000..5cdba6f3bb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-event-gateway.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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-event-gateway', + templateUrl: './diagram-event-gateway.component.html' +}) +export class DiagramEventGatewayComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + centerPentagon: any = {}; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 0.5}; + + circleRadiusInner = 10.4; + circleRadiusOuter = 11.7; + + pentaStrokeWidth = 1.39999998; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x + (this.data.width / 2); + this.center.y = this.data.y + (this.data.height / 2); + this.centerPentagon.x = this.data.x; + this.centerPentagon.y = this.data.y; + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-exclusive-gateway.component.html b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-exclusive-gateway.component.html new file mode 100644 index 0000000000..a00dc1cad3 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-exclusive-gateway.component.html @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-exclusive-gateway.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-exclusive-gateway.component.ts new file mode 100644 index 0000000000..be066cf842 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-exclusive-gateway.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-exclusive-gateway', + templateUrl: './diagram-exclusive-gateway.component.html' +}) +export class DiagramExclusiveGatewayComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + width: any; + height: any; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 3}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x; + this.center.y = this.data.y; + this.width = this.data.width; + this.height = this.data.height; + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-gateway.component.html b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-gateway.component.html new file mode 100644 index 0000000000..6550ba6dca --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-gateway.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-gateway.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-gateway.component.ts new file mode 100644 index 0000000000..a0aadd43e2 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-gateway.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-gateway', + templateUrl: './diagram-gateway.component.html' +}) +export class DiagramGatewayComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + width: any; + height: any; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 2}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x; + this.center.y = this.data.y; + this.width = this.data.width; + this.height = this.data.height; + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-inclusive-gateway.component.html b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-inclusive-gateway.component.html new file mode 100644 index 0000000000..f0c51dc5c1 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-inclusive-gateway.component.html @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-inclusive-gateway.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-inclusive-gateway.component.ts new file mode 100644 index 0000000000..410c83d179 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-inclusive-gateway.component.ts @@ -0,0 +1,49 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-inclusive-gateway', + templateUrl: './diagram-inclusive-gateway.component.html' +}) +export class DiagramInclusiveGatewayComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + width: any; + height: any; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 2.5, radius: 9.75}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x + (this.data.width / 2); + this.center.y = this.data.y + (this.data.height / 2); + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-parallel-gateway.component.html b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-parallel-gateway.component.html new file mode 100644 index 0000000000..434135a5b7 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-parallel-gateway.component.html @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-parallel-gateway.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-parallel-gateway.component.ts new file mode 100644 index 0000000000..20c2d84642 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/diagram-parallel-gateway.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-parallel-gateway', + templateUrl: './diagram-parallel-gateway.component.html' +}) +export class DiagramParallelGatewayComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + width: any; + height: any; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 3}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x; + this.center.y = this.data.y; + this.width = this.data.width; + this.height = this.data.height; + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/gateways/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/gateways/index.ts new file mode 100644 index 0000000000..cdf7470711 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/gateways/index.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 { DiagramGatewayComponent } from './diagram-gateway.component'; +import { DiagramExclusiveGatewayComponent } from './diagram-exclusive-gateway.component'; +import { DiagramInclusiveGatewayComponent } from './diagram-inclusive-gateway.component'; +import { DiagramParallelGatewayComponent } from './diagram-parallel-gateway.component'; +import { DiagramEventGatewayComponent } from './diagram-event-gateway.component'; + +// primitives +export * from './diagram-gateway.component'; +export * from './diagram-exclusive-gateway.component'; +export * from './diagram-inclusive-gateway.component'; +export * from './diagram-parallel-gateway.component'; +export * from './diagram-event-gateway.component'; + +export const DIAGRAM_GATEWAY_DIRECTIVES: any[] = [ + DiagramGatewayComponent, + DiagramExclusiveGatewayComponent, + DiagramInclusiveGatewayComponent, + DiagramParallelGatewayComponent, + DiagramEventGatewayComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-container-icon-event.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-container-icon-event.component.html new file mode 100644 index 0000000000..00ffb44abd --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-container-icon-event.component.html @@ -0,0 +1,14 @@ +
+
+ +
+
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-container-icon-event.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-container-icon-event.component.ts new file mode 100644 index 0000000000..fdfc433659 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-container-icon-event.component.ts @@ -0,0 +1,44 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-container-icon-event', + templateUrl: './diagram-container-icon-event.component.html' +}) +export class DiagramContainerIconEventTaskComponent { + @Input() + data: any; + + @Input() + type: string; + + @Input() + fillColor: string; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-alfresco-publish-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-alfresco-publish-task.component.html new file mode 100644 index 0000000000..65394e97f8 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-alfresco-publish-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-alfresco-publish-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-alfresco-publish-task.component.ts new file mode 100644 index 0000000000..ecf69baf43 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-alfresco-publish-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-alfresco-publish-task', + templateUrl: './diagram-icon-alfresco-publish-task.component.html' +}) +export class DiagramIconAlfrescoPublishTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 4}; + this.options.stroke = 'none' ; + this.options.fillColors = '#87C040' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-box-publish-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-box-publish-task.component.html new file mode 100644 index 0000000000..61548c14e2 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-box-publish-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-box-publish-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-box-publish-task.component.ts new file mode 100644 index 0000000000..ec58fbf9d6 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-box-publish-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-box-publish-task', + templateUrl: './diagram-icon-box-publish-task.component.html' +}) +export class DiagramIconBoxPublishTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 6, y: this.data.y + 6}; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-business-rule-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-business-rule-task.component.html new file mode 100644 index 0000000000..fe8358e3e3 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-business-rule-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-business-rule-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-business-rule-task.component.ts new file mode 100644 index 0000000000..8e7064fa62 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-business-rule-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-business-rule-task', + templateUrl: './diagram-icon-business-rule-task.component.html' +}) +export class DiagramIconBusinessRuleTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 4}; + this.options.stroke = 'none' ; + this.options.fillColors = '#72a7d0' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-camel-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-camel-task.component.html new file mode 100644 index 0000000000..7404657ae5 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-camel-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-camel-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-camel-task.component.ts new file mode 100644 index 0000000000..12f8ed8219 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-camel-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-camel-task', + templateUrl: './diagram-icon-camel-task.component.html' +}) +export class DiagramIconCamelTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 8, y: this.data.y + 6}; + this.options.stroke = 'none' ; + this.options.fillColors = '#bd4848' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-error.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-error.component.html new file mode 100644 index 0000000000..2355c39b1a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-error.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-error.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-error.component.ts new file mode 100644 index 0000000000..d757913222 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-error.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-error', + templateUrl: './diagram-icon-error.component.html' +}) +export class DiagramIconErrorComponent { + @Input() + data: any; + + @Input() + fillColor: string; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x - 1, y: this.data.y - 1}; + + this.options.stroke = 'black'; + this.options.fillColors = this.fillColor; + this.options.strokeWidth = 1; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-google-drive-publish-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-google-drive-publish-task.component.html new file mode 100644 index 0000000000..93776ac4d7 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-google-drive-publish-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-google-drive-publish-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-google-drive-publish-task.component.ts new file mode 100644 index 0000000000..3eebba0225 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-google-drive-publish-task.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-google-drive-publish-task', + templateUrl: './diagram-icon-google-drive-publish-task.component.html' +}) +export class DiagramIconGoogleDrivePublishTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 6, y: this.data.y + 6}; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-manual-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-manual-task.component.html new file mode 100644 index 0000000000..fbd042895e --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-manual-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-manual-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-manual-task.component.ts new file mode 100644 index 0000000000..d0f2af97eb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-manual-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-manual-task', + templateUrl: './diagram-icon-manual-task.component.html' +}) +export class DiagramIconManualTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 4}; + this.options.stroke = 'none' ; + this.options.fillColors = '#d1b575' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-message.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-message.component.html new file mode 100644 index 0000000000..6f4870c1fb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-message.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-message.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-message.component.ts new file mode 100644 index 0000000000..7564a23485 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-message.component.ts @@ -0,0 +1,47 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-message', + templateUrl: './diagram-icon-message.component.html' +}) +export class DiagramIconMessageComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 6, y: this.data.y + 6}; + + this.options.stroke = 'none'; + this.options.fillColors = '#585858'; + this.options.strokeWidth = 1; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-mule-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-mule-task.component.html new file mode 100644 index 0000000000..0b42e1fd3c --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-mule-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-mule-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-mule-task.component.ts new file mode 100644 index 0000000000..fce69f4697 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-mule-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-mule-task', + templateUrl: './diagram-icon-mule-task.component.html' +}) +export class DiagramIconMuleTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 2, y: this.data.y + 2}; + this.options.stroke = 'none' ; + this.options.fillColors = '#bd4848' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-receive-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-receive-task.component.html new file mode 100644 index 0000000000..9ca2752fad --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-receive-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-receive-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-receive-task.component.ts new file mode 100644 index 0000000000..ca67abe1f4 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-receive-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-receive-task', + templateUrl: './diagram-icon-receive-task.component.html' +}) +export class DiagramIconReceiveTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 2}; + this.options.stroke = 'none' ; + this.options.fillColors = '#16964d' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-rest-call-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-rest-call-task.component.html new file mode 100644 index 0000000000..6621df0e84 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-rest-call-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-rest-call-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-rest-call-task.component.ts new file mode 100644 index 0000000000..87c45074f3 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-rest-call-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-rest-call-task', + templateUrl: './diagram-icon-rest-call-task.component.html' +}) +export class DiagramIconRestCallTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 2, y: this.data.y + 2}; + this.options.stroke = 'none' ; + this.options.fillColors = '#bd4848' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-script-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-script-task.component.html new file mode 100644 index 0000000000..d3a6cacdc3 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-script-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-script-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-script-task.component.ts new file mode 100644 index 0000000000..498ec86b44 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-script-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-script-task', + templateUrl: './diagram-icon-script-task.component.html' +}) +export class DiagramIconScriptTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 4}; + this.options.stroke = 'none' ; + this.options.fillColors = '#72a7d0' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-send-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-send-task.component.html new file mode 100644 index 0000000000..50185da5f6 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-send-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-send-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-send-task.component.ts new file mode 100644 index 0000000000..9e9f6cc8b7 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-send-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-send-task', + templateUrl: './diagram-icon-send-task.component.html' +}) +export class DiagramIconSendTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 4}; + this.options.stroke = 'none' ; + this.options.fillColors = '#16964d' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-service-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-service-task.component.html new file mode 100644 index 0000000000..1b903589eb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-service-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-service-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-service-task.component.ts new file mode 100644 index 0000000000..d1973a62b2 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-service-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-service-task', + templateUrl: './diagram-icon-service-task.component.html' +}) +export class DiagramIconServiceTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 4}; + this.options.stroke = 'none' ; + this.options.fillColors = '#72a7d0' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-signal.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-signal.component.html new file mode 100644 index 0000000000..3f926dce44 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-signal.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-signal.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-signal.component.ts new file mode 100644 index 0000000000..aed37eab01 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-signal.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-signal', + templateUrl: './diagram-icon-signal.component.html' +}) +export class DiagramIconSignalComponent { + @Input() + data: any; + + @Input() + fillColor: string; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x - 1, y: this.data.y - 1}; + + this.options.stroke = 'black'; + this.options.fillColors = this.fillColor; + this.options.strokeWidth = 1; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-timer.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-timer.component.html new file mode 100644 index 0000000000..1bb93fff2a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-timer.component.html @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-timer.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-timer.component.ts new file mode 100644 index 0000000000..29657ce05a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-timer.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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-timer', + templateUrl: './diagram-icon-timer.component.html' +}) +export class DiagramIconTimerComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + position: any; + + circleRadius: number; + + circleOptions: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + timerOptions: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x + (this.data.width / 2); + this.center.y = this.data.y + (this.data.height / 2); + this.circleRadius = 10; + this.position = {x: this.data.x + 5, y: this.data.y + 5}; + + this.circleOptions.stroke = 'black' ; + this.circleOptions.fillColors = 'none' ; + this.timerOptions.stroke = 'none' ; + this.timerOptions.fillColors = '#585858' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-user-task.component.html b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-user-task.component.html new file mode 100644 index 0000000000..f7ff508324 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-user-task.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-user-task.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-user-task.component.ts new file mode 100644 index 0000000000..902b0baf6d --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/diagram-icon-user-task.component.ts @@ -0,0 +1,45 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-icon-user-task', + templateUrl: './diagram-icon-user-task.component.html' +}) +export class DiagramIconUserTaskComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + position: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: ''}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.position = {x: this.data.x + 4, y: this.data.y + 4}; + this.options.stroke = 'none' ; + this.options.fillColors = '#d1b575' ; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/icons/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/icons/index.ts new file mode 100644 index 0000000000..70777e92bc --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/icons/index.ts @@ -0,0 +1,76 @@ +/*! + * @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 { DiagramIconServiceTaskComponent } from './diagram-icon-service-task.component'; +import { DiagramIconSendTaskComponent } from './diagram-icon-send-task.component'; +import { DiagramIconUserTaskComponent } from './diagram-icon-user-task.component'; +import { DiagramIconManualTaskComponent } from './diagram-icon-manual-task.component'; +import { DiagramIconCamelTaskComponent } from './diagram-icon-camel-task.component'; +import { DiagramIconMuleTaskComponent } from './diagram-icon-mule-task.component'; +import { DiagramIconAlfrescoPublishTaskComponent } from './diagram-icon-alfresco-publish-task.component'; +import { DiagramIconRestCallTaskComponent } from './diagram-icon-rest-call-task.component'; +import { DiagramIconGoogleDrivePublishTaskComponent } from './diagram-icon-google-drive-publish-task.component'; +import { DiagramIconBoxPublishTaskComponent } from './diagram-icon-box-publish-task.component'; +import { DiagramIconReceiveTaskComponent } from './diagram-icon-receive-task.component'; +import { DiagramIconScriptTaskComponent } from './diagram-icon-script-task.component'; +import { DiagramIconBusinessRuleTaskComponent } from './diagram-icon-business-rule-task.component'; +import { DiagramContainerIconEventTaskComponent } from './diagram-container-icon-event.component'; +import { DiagramIconTimerComponent } from './diagram-icon-timer.component'; +import { DiagramIconErrorComponent } from './diagram-icon-error.component'; +import { DiagramIconSignalComponent } from './diagram-icon-signal.component'; +import { DiagramIconMessageComponent } from './diagram-icon-message.component'; + +// primitives +export * from './diagram-icon-service-task.component'; +export * from './diagram-icon-send-task.component'; +export * from './diagram-icon-user-task.component'; +export * from './diagram-icon-manual-task.component'; +export * from './diagram-icon-camel-task.component'; +export * from './diagram-icon-mule-task.component'; +export * from './diagram-icon-alfresco-publish-task.component'; +export * from './diagram-icon-rest-call-task.component'; +export * from './diagram-icon-google-drive-publish-task.component'; +export * from './diagram-icon-box-publish-task.component'; +export * from './diagram-icon-receive-task.component'; +export * from './diagram-icon-script-task.component'; +export * from './diagram-icon-business-rule-task.component'; +export * from './diagram-container-icon-event.component'; +export * from './diagram-icon-timer.component'; +export * from './diagram-icon-error.component'; +export * from './diagram-icon-signal.component'; +export * from './diagram-icon-message.component'; + +export const DIAGRAM_ICONS_DIRECTIVES: any[] = [ + DiagramIconServiceTaskComponent, + DiagramIconSendTaskComponent, + DiagramIconUserTaskComponent, + DiagramIconManualTaskComponent, + DiagramIconCamelTaskComponent, + DiagramIconMuleTaskComponent, + DiagramIconAlfrescoPublishTaskComponent, + DiagramIconRestCallTaskComponent, + DiagramIconGoogleDrivePublishTaskComponent, + DiagramIconBoxPublishTaskComponent, + DiagramIconReceiveTaskComponent, + DiagramIconScriptTaskComponent, + DiagramIconBusinessRuleTaskComponent, + DiagramContainerIconEventTaskComponent, + DiagramIconTimerComponent, + DiagramIconErrorComponent, + DiagramIconSignalComponent, + DiagramIconMessageComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/index.ts new file mode 100644 index 0000000000..b6346365b9 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/index.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. + */ + +import { DiagramComponent } from './diagram.component'; +import { DiagramSequenceFlowComponent } from './diagram-sequence-flow.component'; +import { DIAGRAM_ACTIVITIES_DIRECTIVES } from './activities/index'; +import { DIAGRAM_EVENTS_DIRECTIVES } from './events/index'; +import { DIAGRAM_GATEWAY_DIRECTIVES } from './gateways/index'; +import { DIAGRAM_ICONS_DIRECTIVES } from './icons/index'; +import { DIAGRAM_BOUNDARY_EVENTS_DIRECTIVES } from './boundary-events/index'; +import { DIAGRAM_INTERMEDIATE_EVENTS_DIRECTIVES } from './intermediate-catching-events/index'; +import { DIAGRAM_STRUCTURAL_DIRECTIVES } from './structural/index'; +import { DIAGRAM_SWIMLANES_DIRECTIVES } from './swimlanes/index'; + +import { DiagramColorService } from '../services/diagram-color.service'; +import { DiagramsService } from '../services/diagrams.service'; + +// primitives +export * from './diagram.component'; +export * from './events/index'; +export * from './activities/index'; +export * from './icons/index'; +export * from './diagram-sequence-flow.component'; +export * from './boundary-events/index'; +export * from './intermediate-catching-events/index'; +export * from './structural/index'; +export * from './swimlanes/index'; + +export const DIAGRAM_DIRECTIVES: any[] = [ + DiagramComponent, + DIAGRAM_EVENTS_DIRECTIVES, + DIAGRAM_ACTIVITIES_DIRECTIVES, + DiagramSequenceFlowComponent, + DIAGRAM_GATEWAY_DIRECTIVES, + DIAGRAM_ICONS_DIRECTIVES, + DIAGRAM_BOUNDARY_EVENTS_DIRECTIVES, + DIAGRAM_INTERMEDIATE_EVENTS_DIRECTIVES, + DIAGRAM_STRUCTURAL_DIRECTIVES, + DIAGRAM_SWIMLANES_DIRECTIVES +]; + +export const DIAGRAM_PROVIDERS: any[] = [ + DiagramsService, + DiagramColorService +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/diagram-intermediate-catching-event.component.html b/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/diagram-intermediate-catching-event.component.html new file mode 100644 index 0000000000..b5eb151f55 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/diagram-intermediate-catching-event.component.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/diagram-intermediate-catching-event.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/diagram-intermediate-catching-event.component.ts new file mode 100644 index 0000000000..c03b144865 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/diagram-intermediate-catching-event.component.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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-intermediate-catching-event', + templateUrl: './diagram-intermediate-catching-event.component.html' +}) +export class DiagramIntermediateCatchingEventComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + center: any = {}; + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: 1}; + + circleRadiusInner: number; + circleRadiusOuter: number; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.center.x = this.data.x + (this.data.width / 2); + this.center.y = this.data.y + (this.data.height / 2); + + this.circleRadiusInner = 12; + this.circleRadiusOuter = 15; + + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.fillColors = this.diagramColorService.getFillColour(this.data.id); + this.options.fillOpacity = this.diagramColorService.getFillOpacity(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/index.ts new file mode 100644 index 0000000000..bfebc82955 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/intermediate-catching-events/index.ts @@ -0,0 +1,25 @@ +/*! + * @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 { DiagramIntermediateCatchingEventComponent } from './diagram-intermediate-catching-event.component'; + +// primitives +export * from './diagram-intermediate-catching-event.component'; + +export const DIAGRAM_INTERMEDIATE_EVENTS_DIRECTIVES: any[] = [ + DiagramIntermediateCatchingEventComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/declarations.d.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/declarations.d.ts new file mode 100644 index 0000000000..7b345fa61a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/declarations.d.ts @@ -0,0 +1,19 @@ +/*! + * @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. + */ + +declare let Raphael: any; +declare let Polyline: any; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/index.ts new file mode 100644 index 0000000000..7605b66ab6 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/index.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 { RaphaelIconServiceDirective } from './raphael-icon-service.component'; +import { RaphaelIconSendDirective } from './raphael-icon-send.component'; +import { RaphaelIconUserDirective } from './raphael-icon-user.component'; +import { RaphaelIconManualDirective } from './raphael-icon-manual.component'; +import { RaphaelIconCamelDirective } from './raphael-icon-camel.component'; +import { RaphaelIconMuleDirective } from './raphael-icon-mule.component'; +import { RaphaelIconAlfrescoPublishDirective } from './raphael-icon-alfresco-publish.component'; +import { RaphaelIconRestCallDirective } from './raphael-icon-rest-call.component'; +import { RaphaelIconGoogleDrivePublishDirective } from './raphael-icon-google-drive-publish.component'; +import { RaphaelIconBoxPublishDirective } from './raphael-icon-box-publish.component'; +import { RaphaelIconReceiveDirective } from './raphael-icon-receive.component'; +import { RaphaelIconScriptDirective } from './raphael-icon-script.component'; +import { RaphaelIconBusinessRuleDirective } from './raphael-icon-business-rule.component'; +import { RaphaelIconTimerDirective } from './raphael-icon-timer.component'; +import { RaphaelIconErrorDirective } from './raphael-icon-error.component'; +import { RaphaelIconSignalDirective } from './raphael-icon-signal.component'; +import { RaphaelIconMessageDirective } from './raphael-icon-message.component'; + +// primitives +export * from './raphael-icon-service.component'; +export * from './raphael-icon-send.component'; +export * from './raphael-icon-user.component'; +export * from './raphael-icon-manual.component'; +export * from './raphael-icon-camel.component'; +export * from './raphael-icon-mule.component'; +export * from './raphael-icon-alfresco-publish.component'; +export * from './raphael-icon-rest-call.component'; +export * from './raphael-icon-google-drive-publish.component'; +export * from './raphael-icon-box-publish.component'; +export * from './raphael-icon-receive.component'; +export * from './raphael-icon-script.component'; +export * from './raphael-icon-business-rule.component'; +export * from './raphael-icon-timer.component'; +export * from './raphael-icon-error.component'; +export * from './raphael-icon-signal.component'; +export * from './raphael-icon-message.component'; + +export const RAPHAEL_ICONS_DIRECTIVES: any[] = [ + RaphaelIconServiceDirective, + RaphaelIconSendDirective, + RaphaelIconUserDirective, + RaphaelIconManualDirective, + RaphaelIconCamelDirective, + RaphaelIconMuleDirective, + RaphaelIconAlfrescoPublishDirective, + RaphaelIconRestCallDirective, + RaphaelIconGoogleDrivePublishDirective, + RaphaelIconBoxPublishDirective, + RaphaelIconReceiveDirective, + RaphaelIconScriptDirective, + RaphaelIconBusinessRuleDirective, + RaphaelIconTimerDirective, + RaphaelIconErrorDirective, + RaphaelIconSignalDirective, + RaphaelIconMessageDirective +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-alfresco-publish.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-alfresco-publish.component.ts new file mode 100644 index 0000000000..878b094511 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-alfresco-publish.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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-alfresco-publish'}) +export class RaphaelIconAlfrescoPublishDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + + let startX = position.x + 2; + let startY = position.y + 2; + + let path1 = this.paper.path(`M4.11870968,2.12890323 L6.12954839,0.117935484 L3.10993548,0.118064516 L3.10270968,0.118064516 + C1.42941935,0.118064516 0.0729032258,1.47458065 0.0729032258,3.14774194 C0.0729032258,4.82116129 1.42929032,6.17754839 + 3.10258065,6.17754839 C3.22967742,6.17754839 3.35470968,6.16877419 3.47767742,6.15354839 C2.8163871,4.85083871 + 3.02954839,3.21793548 4.11870968,2.12890323M6.57032258,3.144 L6.57032258,0.300258065 L4.43522581,2.4356129 L4.43006452,2.44064516 + C3.24683871,3.62387097 3.24683871,5.54219355 4.43006452,6.72541935 C5.61329032,7.90864516 7.5316129,7.90864516 + 8.71483871,6.72541935 C8.80464516,6.6356129 8.88529032,6.54025806 8.96154839,6.44270968 C7.57341935,5.98864516 + 6.57045161,4.68387097 6.57032258,3.144`).attr({ + 'stroke': this.stroke, + 'fill': '#87C040', + 'stroke-width': this.strokeWidth + }); + + let startX1 = startX + 1.419355; + let startY1 = startY + 8.387097; + path1.transform('T' + startX1 + ',' + startY1); + + path1 = this.paper.path(`M10.4411613,10.5153548 L8.43032258,8.50451613 L8.43032258,11.5313548 C8.43032258,13.2047742 9.78683871, + 14.5611613 11.460129,14.5611613 C13.1334194,14.5611613 14.4899355,13.2047742 14.4899355,11.5314839 C14.4899355,11.4043871 + 14.4811613,11.2793548 14.4659355,11.1563871 C13.1632258,11.8178065 11.5303226,11.6045161 10.4411613,10.5153548M15.0376774, + 5.91935484 C14.947871,5.82954839 14.8526452,5.74890323 14.7550968,5.67264516 C14.3010323,7.06064516 12.996129,8.06374194 + 11.4563871,8.06374194 L8.61277419,8.06374194 L10.7529032,10.204 C11.936129,11.3872258 13.8545806,11.3872258 15.0376774,10.204 + C16.2209032,9.02077419 16.2209032,7.10245161 15.0376774,5.91935484`).attr({ + 'stroke': this.stroke, + 'fill': '#87C040', + 'stroke-width': this.strokeWidth + }); + path1.transform('T' + startX + ',' + startY); + + path1 = this.paper.path(`M5.9083871,1.5636129 C5.78129032,1.5636129 5.65625806,1.57225806 5.53329032,1.58748387 + C6.19458065,2.89032258 5.98141935,4.52309677 4.89225806,5.61225806 L2.88154839,7.62309677 L5.9083871,7.62309677 + C7.58154839,7.62309677 8.93806452,6.26658065 8.93806452,4.59329032 C8.93819355,2.92 7.58167742,1.5636129 + 5.9083871,1.5636129`).attr({ + 'stroke': this.stroke, + 'fill': '#ED9A2D', + 'stroke-width': this.strokeWidth + }); + + let startX2 = startX + 5.548387; + path1.transform('T' + startX2 + ',' + startY); + + path1 = this.paper.path(`M4.58090323,1.0156129 C3.39767742,-0.167483871 1.47935484,-0.167483871 0.296129032,1.01574194 + C0.206451613,1.10554839 0.125806452,1.20077419 0.0495483871,1.29845161 C1.43754839,1.75251613 2.44064516,3.05729032 + 2.44064516,4.59703226 L2.44064516,7.44077419 L4.57574194,5.30554839 L4.58090323,5.30051613 C5.76412903,4.11729032 + 5.76412903,2.19896774 4.58090323,1.0156129`).attr({ + 'stroke': this.stroke, + 'fill': '#5698C6', + 'stroke-width': this.strokeWidth + }); + path1.transform('T' + startX2 + ',' + startY); + + path1 = this.paper.path(`M5.54051613,5.61432258 L5.62670968,5.70425806 L7.54632258,7.62387097 L7.5483871,7.62387097 + L7.5483871,4.604 L7.5483871,4.59677419 C7.5483871,2.92348387 6.19187097,1.56696774 4.51858065,1.56696774 C2.84529032,1.56696774 + 1.48877419,2.92335484 1.48890323,4.59664516 C1.48890323,4.72348387 1.49754839,4.84812903 1.51264516,4.97083871 + C2.81625806,4.30993548 4.45122581,4.52503226 5.54051613,5.61432258M1.23251613,10.4292903 C1.25625806,10.3588387 + 1.28180645,10.2894194 1.30980645,10.2210323 C1.31329032,10.2123871 1.3163871,10.2036129 1.32,10.1952258 C1.35070968,10.1216774 + 1.38451613,10.0500645 1.42,9.97935484 C1.42774194,9.96374194 1.43574194,9.9483871 1.44387097,9.93277419 C1.4803871,9.86258065 + 1.51883871,9.79354839 1.55987097,9.72632258 C1.56425806,9.71909677 1.56903226,9.71225806 1.57341935,9.70529032 + C1.6123871,9.64245161 1.65354839,9.58141935 1.6963871,9.52141935 C1.70516129,9.50903226 1.71380645,9.49651613 + 1.72283871,9.48425806 C1.76890323,9.42154839 1.81690323,9.36064516 1.86683871,9.30129032 C1.87703226,9.28916129 + 1.88735484,9.27741935 1.89780645,9.26567742 C1.94658065,9.20916129 1.99690323,9.15406452 2.04916129,9.10090323 + C2.05380645,9.09625806 2.05806452,9.09135484 2.06270968,9.08670968 C2.11832258,9.03083871 2.17625806,8.97741935 + 2.23548387,8.92554839 C2.2483871,8.91419355 2.26129032,8.90296774 2.27432258,8.89187097 C2.33393548,8.84103226 + 2.39496774,8.79212903 2.45780645,8.74529032 C2.46606452,8.73922581 2.47470968,8.73354839 2.48296774,8.7276129 + C2.54167742,8.68490323 2.60180645,8.64412903 2.66322581,8.60503226 C2.67535484,8.59729032 2.68735484,8.58929032 + 2.6996129,8.58167742 C2.76593548,8.54064516 2.83380645,8.50206452 2.90296774,8.46541935 C2.91754839,8.45780645 + 2.93225806,8.45045161 2.94696774,8.44296774 C3.016,8.40774194 3.08593548,8.37406452 3.15741935,8.34348387 C3.16090323,8.34206452 + 3.16425806,8.3403871 3.16774194,8.33883871 C3.24167742,8.30748387 3.31729032,8.27948387 3.39380645,8.25316129 + C3.41032258,8.24748387 3.42670968,8.24180645 3.44335484,8.2363871 C3.51909677,8.21174194 3.59587097,8.18903226 + 3.67380645,8.16929032 C3.68567742,8.16645161 3.69793548,8.16387097 3.70980645,8.16116129 C3.78206452,8.14374194 + 3.85509677,8.12877419 3.92890323,8.116 C3.94270968,8.11367742 3.9563871,8.11083871 3.97019355,8.10877419 C4.05032258,8.09587097 + 4.13148387,8.08619355 4.21329032,8.07896774 C4.23096774,8.07741935 4.24877419,8.07625806 4.26645161,8.07483871 + C4.35109677,8.06877419 4.43612903,8.06451613 4.52232258,8.06451613 L7.36606452,8.0643871 L5.22580645,5.92412903 + C4.04258065,4.74103226 2.12412903,4.74090323 0.941032258,5.92412903 C-0.242193548,7.10735484 -0.242193548,9.02567742 + 0.941032258,10.2089032 C1.03070968,10.2985806 1.12464516,10.3814194 1.22206452,10.4575484 C1.22529032,10.448 1.22929032,10.4388387 + 1.23251613,10.4292903`).attr({ + 'stroke': this.stroke, + 'fill': '#5698C6', + 'stroke-width': this.strokeWidth + }); + path1.transform('T' + startX + ',' + startY); + + path1 = this.paper.path(`M5.23290323,5.92412903 L6.92748387,7.61870968 L4.64980645,7.61870968 L4.52064516,7.62141935 + C3.13354839,7.62141935 1.96425806,6.68929032 1.60477419,5.41729032 C2.75870968,4.77019355 4.24619355,4.93754839 + 5.22787097,5.91909677 L5.23290323,5.92412903M7.54722581,4.59612903 L7.54722581,6.99264516 L5.93664516,5.38206452 + L5.84348387,5.29264516 C4.86258065,4.31187097 4.69483871,2.82580645 5.34012903,1.67225806 C6.61367742,2.03070968 + 7.54722581,3.20090323 7.54722581,4.58890323 L7.54722581,4.59612903M10.1385806,5.29819355 L8.444,6.99290323 L8.444,4.71522581 + L8.44129032,4.58606452 C8.44129032,3.19896774 9.37341935,2.02954839 10.6454194,1.67019355 C11.2925161,2.82412903 + 11.1251613,4.3116129 10.1436129,5.29316129 L10.1385806,5.29819355`).attr({ + 'stroke': this.stroke, + 'fill': '#446BA5', + 'stroke-width': this.strokeWidth + }); + path1.transform('T' + startX + ',' + startY); + + path1 = this.paper.path(`M11.4548387,7.61677419 L9.05832258,7.61677419 L10.6689032,6.00619355 L10.7583226,5.91303226 + C11.7390968,4.93212903 13.2251613,4.7643871 14.3787097,5.40967742 C14.0202581,6.68322581 12.8500645,7.61677419 + 11.4620645,7.61677419 L11.4548387,7.61677419`).attr({ + 'stroke': this.stroke, + 'fill': '#FFF101', + 'stroke-width': this.strokeWidth + }); + path1.transform('T' + startX + ',' + startY); + + path1 = this.paper.path(`M10.7470968,10.192 L9.05251613,8.49741935 L11.3301935,8.49741935 L11.4593548,8.49470968 + C12.8464516,8.49483871 14.0157419,9.42696774 14.3752258,10.6989677 C13.2211613,11.3459355 11.7338065,11.1787097 + 10.752129,10.1970323 L10.7470968,10.192M8.43729032,11.5174194 L8.43729032,9.12090323 L10.047871,10.7314839 L10.1411613,10.8209032 + C11.1219355,11.8018065 11.2896774,13.2876129 10.6443871,14.4412903 C9.37083871,14.0828387 8.43729032,12.9127742 + 8.43729032,11.5245161 L8.43729032,11.5174194M5.86193548,10.8296774 L7.55651613,9.13496774 L7.55651613,11.4126452 + L7.55922581,11.5418065 C7.55922581,12.9289032 6.62709677,14.0983226 5.35509677,14.4578065 C4.708,13.3036129 4.87535484,11.8162581 + 5.85690323,10.8347097 L5.86193548,10.8296774M4.53251613,8.50993548 L6.92903226,8.50993548 L5.31845161,10.1205161 + L5.22903226,10.2136774 C4.24812903,11.1945806 2.76219355,11.3623226 1.60851613,10.7170323 C1.96709677,9.44335484 + 3.13716129,8.50993548 4.52529032,8.50993548 L4.53251613,8.50993548`).attr({ + 'stroke': this.stroke, + 'fill': '#45AB47', + 'stroke-width': this.strokeWidth + }); + path1.transform('T' + startX + ',' + startY); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-box-publish.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-box-publish.component.ts new file mode 100644 index 0000000000..545d4b0c63 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-box-publish.component.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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-box-publish'}) +export class RaphaelIconBoxPublishDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + + let image = this.paper.image(); + + image.attr({'x': position.x}); + image.attr({'y': position.y}); + image.attr({'id': 'image3398'}); + image.attr({'preserveAspectRatio': 'none'}); + image.attr({'height': '16'}); + image.attr({'width': '17'}); + image.attr({'src': `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAjCAYAAADxG9hnAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI WXMAAA7DAAAO + wwHHb6hkAAAAB3RJTUUH3wQXDxwCFNe28AAACsdJREFUWMOVmGmMXtV5x3/POfe+ y2yeGc/mwbMZG9tDMGBTKC5GCU4pbYmSRlmowlIFJWnUprSiy4dI/UA/9EsVifZDmy + ZIxEpo1Cql UKICqbABG+x4X4M9iz2bxzOefXnfee+95zz9cN9Z7NqVeqSje3XPec/5n/Ns//8rAAd6xvj0lmYA VJWTI9fN2d5BikslrDEYY0ABIX0aUBUUUJS1TZB0HZS + lUsz8fBHF09neKnFU0t964B5trcoqwA8O fsI3HtmW/u7Hx3p5+oHNqCr7DpzLX3dh1+xC4Z7p2dnGKE7UGmOsNTYFIuXtUlQqsgJkGSdrgBRL 8dLCYlRUUTa1NrYXiqWZ + jqb6q/vPDHz43S/vXHika0Phr984yUufv5/llfnJ0f473z9/7c+P9E8+ 3zdRCAuJR8SU1zUpBhFWL0BATAqhvIqIlCEIqK65JfDOIHgqpUT3HesW9mxreOWejfUvPfPrm6 + de fu8Sgary2sm+5v84NvT9d85P7Z1zhkwmTyZD+dSAKFI2jyAgqQEMkoJFQR1ZC1W5gMowxAgkzlOI HPMlh3ceFUtCjuPXtap3cuSFL9ybdKrq10RkMfi7Nz42g/P+2YP + 9i3sjm6E6a8snS0+ogBcwIki5 g2AkPXGAsqk+z/YNVWxuyLBxXZ66XIbQQimOmSgkDM9G9IwVODda5OpcEc0YYs3xbs/C73z39VMv Ai8FP/rlqG1vafjLGZenusKn121W + 7jr1TxFE0tsQI/iyibbVBTzZvZ6H22vY1lJDQ0WW27Vrs4uc G5vj/d453jw7yWQxomQqwiODs59T1X1yaHh+17dfPXxswuXAWlTBiCcbQmANiVOiRMBabGgJ8YiB xzqre + W5nM/e31pALLKjiEXTVZW5oxnjAML2UcGJoin86PMbxy9Osr8n3/enu9q8HxVLcsCBVhLkM m+vgs1tquW9DDY1VGUJrSBLPeCHh+Ng8H12Z4+oiPHlXNd9+cANNFVkExTm + HUvYVuCmg0+a9AVXW ZSyfvrORtroKXj6YZf/lJekfnzNB7EPfWak8/UgLj29dzzoLYRAgxiCSRoDz8FBHFc/c18CV2ZjN 9TmachY0wavBi8Gs2V3XRMvab6Lg1QHQWV/J + d/ZspCE3zPTEJHJqaPpJE9j/3Lo+g8Ou5AK95QWv 5gu5jS/oTUBuNc+veU4Xo/5LvUPPB1XJXKmtaQOJBy+G5Xwp3L7pmsXkFmM3g+Y2cwxQH0J3SwVB oF5iNXhA8MwWI + 2bmCzTWVJLPhssZ/fZgbtpJ1jirQpqHuPUcRfHe4zwYZy0JDu89onDw3AB/9f23 OdU/jqriVHH+f3fvFbyCu7FrecyV328eT+c4vDq8ehwCJiRQ9ah68EKMUIqU2FuKCTi + nGFG8rta7 /09bk+XT5Kg3FUgRvFdUlQA1OA9oeiPOe6JE8R7EeMQYAgSngHN4XWN5gdAYjKRm8gqJdxgVQptG W+I9qecp1gjGGiLn0nKhgvfg1BMYsXgVVD1GhSRRojiB + QBgan2N4bIal2FNRmWFLWwP1VXmiOCaw lmKUcGHkOlOzi6gIrfXVbG5vpFhIOHplmNb6dWxsqsEliljPxGLCJ5eHufuuNirCEJzHe/B4AqVs V1VUDbFXPCH/fbSPn5c8Y + 9MFlhzks8qm5jy/vauL3Xd3cunqJG8e6uHc0CKzxRIiSktVhvs6a7l3 +0b+4d9PcO+2dp7bu536qiyJwr53z3Csd5pvSciD3R2oJqh6BJMC8d7jUcQrSRnUsb4ZjFvioe2 + t 1FXn6RuZ5YPTE4xMR4xMFjk7MMGBs9fobKzm4a2NJAkcPj/I5fEi50fnaaqv5t2TA7TUZXjqM3fz swOXOHB+nHwIHRvW45wDPIpHRctA1OMUrNG0dMdKpS3ywlceYEdH + E3nxLMbCW0f6eW1/H+NjPRSi iIe33sFzn+2mqTbE43li9xb+9tX36BldYPeWOhpmS7x+qJ/J2QJHL00zO1/gxT94lLqqLN4nqfNq enADoF5RlEQdcaLMLSh7793KrrZGq + oMAE+aorgj4zQc6aGsMGC0uEeQse3dtoLU5AyHYTEBbfZ4v fuY+xq8vMF8SvvToVqbmIn52ZJSe0SmeeqybHe1VpJGaRov3HlQxKCu5QhWcV6J4kfaWPCYTgkaI i1CvZM + XQsq6G+SWhtrKC9ZWWRAOs8+ASrEvYvqGK+WKJycWYXXc1cc+mJuYWCmxsruOJnW3ENkS9 T3ORLqcEg1FNP3hVRIR8JsCZkMtjBeKlmIiQJbEk4lmMPSMTc+Airs8sMDn + nMV6JsSQIkVF6xhZA IGOFU73XOXFpDGOy9A5e54MLw5jYk6zsCd4L6srZ22uaDb1Tupqr6Wqs4Y0PL3Cy9xoLhRgflZiZ KfLeyUGOX54jlw0Znlzg5ycG6RufphTHRMUS + V8aK7Hv7LLlcnqxVXnnnIpE37OluoKaqgu/962nO DU0ABvW+nIU9Xj32j1/4sztNmH0mNY1j/boKioWYD341wcELw0wvRPSPFXj98BV+8ovztDXV8IXf 6MAay9tHB+i7O + svkfMzRnkl++NYZLo4ssq1jHQtFz/GeCb766Cb+5Ev3Uygucax3itN9E+zZ0UJF NkiTo+q0c8mbAWVeqmVfMVb53T2bSIzjv45d46f7L7EQRdRVV7Jz60Z+b3c7TzzYydm + +KSozliMX J/no7AmMtWxsruTzezr5te1NvPTKIR75VCuf29NFzsJTj22l7+oivzh2hV8NTdF4d2vqqGXaKZcu Dz1uK6reiZYLFoKx6ZVdHJpm4FqBQqLUVgTc1VpDZ2sd + LooIg4Dx2QIXBme5PlPEWENLXYYdXQ0k GA6dHqSjtZbu9lqS2GMtXJ4ocubSKA99qp3mKluWSaaf0tLzcrF/4PFMde07S7HDeY9XA+IRlMBa UmmTclH1CbEvs3hVjBGsT + TWPR1Dv8c6hCJkgIFGfyohyBbTWYIwldh4rihXBCv2+VHw+ACFOkrI+ AVUHKB5wSZKWt7U0UFZ5qfdpkVzhHivVVSlG0YosWZayPnGIeIwIxhhCa7CizJWWMKoq10avYa1 + d ob5ry7X+HyzNAQkpZ7m5xC9vrrcYMyiBKNnQEEURx48fwyg6MHBlgFwuR2ADjLHp7SzTK13loemp pdxZUX6p8DJrWOqNXcQgRhBjMNZggpBcGOCimJ6+y766pt7Zjs3d + s9bIsyC1dzTXpYuLsLyskbL2 Lj9FTDpuZGVsZW5ZKouk7+k/CZKaQiAwQmgt+UAIrGFgZJSPD//y9Lee/vL3gj0P7XRDV6/9zcGD B39obEBXexvWeJwX3HJNuNFJVo0js + sJPV+XoKpsTkbJUNRj1ZeAGj/JJ/xAffvD+bPuGxn8TkUUB +MdX9mUix99fm5r7eldXV9jR3ka+IpdGg9eyxExt4ctJR8vMWVbASJmHKcYuy1NTHhdEPOphYXGR viuDjA + 4Nz9XlzasvfucPXwCQH+z7Kd949il6e3qCt97d/xdjUzNfyVTUbAyy2bwxqWwymBuqpV/z XMtlRTXVyUawYjDWrgDxeESdxHFpJi4sjqyvrXntxT/65ssAR06dSw/zzz/6 + F7753O8D8NHHR9rO nL+wZ2pqYl0cxyoIoqsm0LK49ehKKLNGp3gE1JRF+9qoU3JBIDVVuZGvffWLZ2oa7rgCcPLMBe7f 0c3/APbD8KaWhlC3AAAAAElFTkSuQmCC`}); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-business-rule.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-business-rule.component.ts new file mode 100644 index 0000000000..c782593f0c --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-business-rule.component.ts @@ -0,0 +1,68 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-business-rule'}) +export class RaphaelIconBusinessRuleDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`m 1,2 0,14 16,0 0,-14 z m 1.45458,5.6000386 2.90906,0 0,2.7999224 -2.90906,0 z m 4.36364,0 8.72718,0 + 0,2.7999224 -8.72718,0 z m -4.36364,4.1998844 2.90906,0 0,2.800116 -2.90906,0 z m + 4.36364,0 8.72718,0 0,2.800116 -8.72718,0 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-camel.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-camel.component.ts new file mode 100644 index 0000000000..81339c4abb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-camel.component.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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-camel'}) +export class RaphaelIconCamelDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`m 8.1878027,15.383782 c -0.824818,-0.3427 0.375093,-1.1925 0.404055,-1.7743 0.230509,-0.8159 + -0.217173,-1.5329 -0.550642,-2.2283 -0.106244,-0.5273 -0.03299,-1.8886005 -0.747194,-1.7818005 -0.712355,0.3776 -0.9225,1.2309005 + -1.253911,1.9055005 -0.175574,1.0874 -0.630353,2.114 -0.775834,3.2123 -0.244009,0.4224 -1.741203,0.3888 -1.554386,-0.1397 + 0.651324,-0.3302 1.13227,-0.9222 1.180246,-1.6705 0.0082,-0.7042 -0.133578,-1.3681 0.302178,-2.0083 0.08617,-0.3202 + 0.356348,-1.0224005 -0.218996,-0.8051 -0.694517,0.2372 -1.651062,0.6128 -2.057645,-0.2959005 -0.696769,0.3057005 -1.102947,-0.611 + -1.393127,-1.0565 -0.231079,-0.6218 -0.437041,-1.3041 -0.202103,-1.9476 -0.185217,-0.7514 -0.39751099,-1.5209 -0.35214999,-2.301 + -0.243425,-0.7796 0.86000899,-1.2456 0.08581,-1.8855 -0.76078999,0.1964 -1.41630099,-0.7569 -0.79351899,-1.2877 0.58743,-0.52829998 + 1.49031699,-0.242 2.09856399,-0.77049998 0.816875,-0.3212 1.256619,0.65019998 1.923119,0.71939998 0.01194,0.7333 -0.0031,1.5042 + -0.18417,2.2232 -0.194069,0.564 -0.811196,1.6968 0.06669,1.9398 0.738382,-0.173 1.095723,-0.9364 1.659041,-1.3729 0.727298,-0.3962 + 1.093982,-1.117 1.344137,-1.8675 0.400558,-0.8287 1.697676,-0.6854 1.955367,0.1758 0.103564,0.5511 0.9073983,1.7538 + 1.2472763,0.6846 0.121868,-0.6687 0.785541,-1.4454 1.518183,-1.0431 0.813587,0.4875 0.658233,1.6033 1.285504,2.2454 + 0.768715,0.8117 1.745394,1.4801 2.196633,2.5469 0.313781,0.8074 0.568552,1.707 0.496624,2.5733 -0.35485,0.8576005 -1.224508,-0.216 + -0.64725,-0.7284 0.01868,-0.3794 -0.01834,-1.3264 -0.370249,-1.3272 -0.123187,0.7586 -0.152778,1.547 -0.10869,2.3154 + 0.270285,0.6662005 1.310741,0.7653005 1.060553,1.6763005 -0.03493,0.9801 0.294343,1.9505 0.148048,2.9272 -0.320479,0.2406 + -0.79575,0.097 -1.185062,0.1512 -0.165725,0.3657 -0.40138,0.921 -1.020848,0.6744 -0.564671,0.1141 -1.246404,-0.266 + -0.578559,-0.7715 0.679736,-0.5602 0.898618,-1.5362 0.687058,-2.3673 -0.529674,-1.108 -1.275984,-2.0954005 -1.839206,-3.1831005 + -0.634619,-0.1004 -1.251945,0.6779 -1.956789,0.7408 -0.6065893,-0.038 -1.0354363,-0.06 -0.8495673,0.6969005 0.01681,0.711 + 0.152396,1.3997 0.157345,2.1104 0.07947,0.7464 0.171287,1.4944 0.238271,2.2351 0.237411,1.0076 -0.687542,1.1488 -1.414811,0.8598 + z m 6.8675483,-1.8379 c 0.114364,-0.3658 0.206751,-1.2704 -0.114466,-1.3553 -0.152626,0.5835 -0.225018,1.1888 -0.227537,1.7919 + 0.147087,-0.1166 0.265559,-0.2643 0.342003,-0.4366 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-error.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-error.component.ts new file mode 100644 index 0000000000..c77ab84943 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-error.component.ts @@ -0,0 +1,68 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-error'}) +export class RaphaelIconErrorDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`M 22.820839,11.171502 L 19.36734,24.58992 L 13.54138,14.281819 L 9.3386512,20.071607 + L 13.048949,6.8323057 L 18.996148,16.132659 L 22.820839,11.171502 z`).attr({ + 'opacity': 1, + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-google-drive-publish.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-google-drive-publish.component.ts new file mode 100644 index 0000000000..065cf35235 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-google-drive-publish.component.ts @@ -0,0 +1,77 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-google-drive-publish'}) +export class RaphaelIconGoogleDrivePublishDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + + let image = this.paper.image(); + + image.attr({'x': position.x}); + image.attr({'y': position.y}); + image.attr({'id': 'image3398'}); + image.attr({'preserveAspectRatio': 'none'}); + image.attr({'height': '16'}); + image.attr({'width': '17'}); + image.attr({'src': `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBA + JqcGAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIHSURBVDiNpVI7a1RREP7mzLl3d+9mScxaiBLFwohxQcXCwjwao/gqFAQhRGOphQgmgs9oGxaV + gFhpYPUPGMFCCzEqCgETg0uK4CuFoLhZyWNf994zFrqy9xJWwQ+mOB8z33wzZ4D/BIWJppG+plstc+mjK9yttbzALHExcoDaRxdqeRUWcFkGBz7G1s152CCQ7dUAqNOLuZf + qOmi439MmhifF86e6uLj4MFXoCuVXWPkp2vZkZlkHYvRNAJYwtz79oXdMLfFMSMD2Dd9YdoSGTO9hQLoBQBESQvLpUNaZD1sGsN8d390dFBjpiwooHVBW6tvXCr2H4EFo6L + wR97pkj9h/BByWfgDrA4lRTWDvHIPOAihVaWO8txCkygu50wBAsbsnWpT2pwHEA/sgXC30Zq4BwJfHHRdY0R4nxp5mbFGEJIB5l2SjVtoMhYsBfC5EikPVh7Z4uFyqnKq43 + hoQFrXCIydCjZbWlyl+79gzCDprq1dPnnyhS8nNZDmvRVmbAIDhKyL5/e2kjKi4pbwxLQZniDAOgAHAybW90aXmncp2xoSsvdVDMWBAAi69sqsvqsLxzARB7vxaMHvJDwcT + ZCVeClnhIwqC5Pb08Kp3CgBUxT4PINc4u+u54uY8FLfXLQa+sx0dRNV2eXSi6OzryK2c7Wkl0msB5OuG0JVsOvnqL03+DT8BxkC5RkIpSlIAAAAASUVORK5CYII=`}); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-manual.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-manual.component.ts new file mode 100644 index 0000000000..9f073711f6 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-manual.component.ts @@ -0,0 +1,76 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-manual'}) +export class RaphaelIconManualDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`m 17,9.3290326 c -0.0069,0.5512461 -0.455166,1.0455894 -0.940778,1.0376604 l -5.792746,0 c + 0.0053,0.119381 0.0026,0.237107 0.0061,0.355965 l 5.154918,0 c 0.482032,-0.0096 0.925529,0.49051 0.919525,1.037574 -0.0078,0.537128 + -0.446283,1.017531 -0.919521,1.007683 l -5.245273,0 c -0.01507,0.104484 -0.03389,0.204081 -0.05316,0.301591 l 2.630175,0 + c 0.454137,-0.0096 0.872112,0.461754 0.866386,0.977186 C 13.619526,14.554106 13.206293,15.009498 12.75924,15 L 3.7753054,15 + C 3.6045812,15 3.433552,14.94423 3.2916363,14.837136 c -0.00174,0 -0.00436,0 -0.00609,0 C 1.7212035,14.367801 + 0.99998255,11.458641 1,11.458641 L 1,7.4588393 c 0,0 0.6623144,-1.316333 1.8390583,-2.0872584 1.1767614,-0.7711868 + 6.8053358,-2.40497 7.2587847,-2.8052901 0.453484,-0.40032 1.660213,1.4859942 0.04775,2.4010487 C 8.5332315,5.882394 + 8.507351,5.7996113 8.4370292,5.7936859 l 6.3569748,-0.00871 c 0.497046,-0.00958 0.952273,0.5097676 0.94612,1.0738232 + -0.0053,0.556126 -0.456176,1.0566566 -0.94612,1.0496854 l -4.72435,0 c 0.01307,0.1149374 0.0244,0.2281319 0.03721,0.3498661 + l 5.952195,0 c 0.494517,-0.00871 0.947906,0.5066305 0.940795,1.0679848 z`).attr({ + 'opacity': 1, + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-message.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-message.component.ts new file mode 100644 index 0000000000..85ea452f61 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-message.component.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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-message'}) +export class RaphaelIconMessageDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`M 1 3 L 9 11 L 17 3 L 1 3 z M 1 5 L 1 13 L 5 9 L 1 5 z M 17 5 L 13 9 L 17 13 L 17 5 z M 6 10 L 1 15 + L 17 15 L 12 10 L 9 13 L 6 10 z`).attr({ + 'opacity': this.fillOpacity, + 'stroke': this.stroke, + 'strokeWidth': this.strokeWidth, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-mule.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-mule.component.ts new file mode 100644 index 0000000000..3bf4e42147 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-mule.component.ts @@ -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. + */ + +import { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-mule'}) +export class RaphaelIconMuleDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`M 8,0 C 3.581722,0 0,3.5817 0,8 c 0,4.4183 3.581722,8 8,8 4.418278,0 8,-3.5817 8,-8 L 16,7.6562 + C 15.813571,3.3775 12.282847,0 8,0 z M 5.1875,2.7812 8,7.3437 10.8125,2.7812 c 1.323522,0.4299 2.329453,1.5645 2.8125,2.8438 + 1.136151,2.8609 -0.380702,6.4569 -3.25,7.5937 -0.217837,-0.6102 -0.438416,-1.2022 -0.65625,-1.8125 0.701032,-0.2274 + 1.313373,-0.6949 1.71875,-1.3125 0.73624,-1.2317 0.939877,-2.6305 -0.03125,-4.3125 l -2.75,4.0625 -0.65625,0 -0.65625,0 -2.75,-4 + C 3.5268433,7.6916 3.82626,8.862 4.5625,10.0937 4.967877,10.7113 5.580218,11.1788 6.28125,11.4062 6.063416,12.0165 5.842837,12.6085 + 5.625,13.2187 2.755702,12.0819 1.238849,8.4858 2.375,5.625 2.858047,4.3457 3.863978,3.2112 5.1875,2.7812 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-receive.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-receive.component.ts new file mode 100644 index 0000000000..6b1aea8a1b --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-receive.component.ts @@ -0,0 +1,67 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-receive'}) +export class RaphaelIconReceiveDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`m 0.5,2.5 0,13 17,0 0,-13 z M 2,4 6.5,8.5 2,13 z M 4,4 14,4 9,9 z m 12,0 0,9 -4.5,-4.5 z + M 7.5,9.5 9,11 10.5,9.5 15,14 3,14 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-rest-call.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-rest-call.component.ts new file mode 100644 index 0000000000..a46e83d67d --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-rest-call.component.ts @@ -0,0 +1,75 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-rest-call'}) +export class RaphaelIconRestCallDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`m 16.704699,5.9229055 q 0.358098,0 0.608767,0.2506681 0.250669,0.250668 0.250669,0.6087677 0,0.3580997 + -0.250669,0.6087677 -0.250669,0.2506679 -0.608767,0.2506679 -0.358098,0 -0.608767,-0.2506679 -0.250669,-0.250668 + -0.250669,-0.6087677 0,-0.3580997 0.250669,-0.6087677 0.250669,-0.2506681 0.608767,-0.2506681 z m 2.578308,-2.0053502 q + -2.229162,0 -3.854034,0.6759125 -1.624871,0.6759067 -3.227361,2.2694472 -0.716197,0.725146 -1.575633,1.7457293 L + 7.2329969,8.7876913 Q 7.0897576,8.8055849 7.000233,8.9309334 L 4.9948821,12.368677 q -0.035811,0.06267 -0.035811,0.143242 + 0,0.107426 0.080572,0.205905 l 0.5729577,0.572957 q 0.125334,0.116384 0.2864786,0.07162 l 2.4708789,-0.760963 2.5156417,2.515645 + -0.76096,2.470876 q -0.009,0.02687 -0.009,0.08057 0,0.125338 0.08058,0.205905 l 0.572957,0.572958 q 0.170096,0.152194 + 0.349146,0.04476 l 3.437744,-2.005351 q 0.125335,-0.08953 0.143239,-0.232763 l 0.17905,-3.392986 q 1.02058,-0.859435 + 1.745729,-1.575629 1.67411,-1.6830612 2.309735,-3.2049805 0.635625,-1.5219191 0.635625,-3.8585111 0,-0.1253369 -0.08505,-0.2148575 + -0.08505,-0.089526 -0.201431,-0.089526 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-script.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-script.component.ts new file mode 100644 index 0000000000..beee45747b --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-script.component.ts @@ -0,0 +1,68 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-script'}) +export class RaphaelIconScriptDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`m 5,2 0,0.094 c 0.23706,0.064 0.53189,0.1645 0.8125,0.375 0.5582,0.4186 1.05109,1.228 1.15625,2.5312 + l 8.03125,0 1,0 1,0 c 0,-3 -2,-3 -2,-3 l -10,0 z M 4,3 4,13 2,13 c 0,3 2,3 2,3 l 9,0 c 0,0 2,0 2,-3 L 15,6 6,6 6,5.5 C 6,4.1111 + 5.5595,3.529 5.1875,3.25 4.8155,2.971 4.5,3 4.5,3 L 4,3 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-send.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-send.component.ts new file mode 100644 index 0000000000..cd2adc4004 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-send.component.ts @@ -0,0 +1,67 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-send'}) +export class RaphaelIconSendDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`M 1 3 L 9 11 L 17 3 L 1 3 z M 1 5 L 1 13 L 5 9 L 1 5 z M 17 5 L 13 9 L 17 13 L 17 5 z M 6 10 L 1 15 + L 17 15 L 12 10 L 9 13 L 6 10 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-service.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-service.component.ts new file mode 100644 index 0000000000..c5db7a5601 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-service.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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-service'}) +export class RaphaelIconServiceDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path('M 8,1 7.5,2.875 c 0,0 -0.02438,0.250763 -0.40625,0.4375 C 7.05724,3.330353 7.04387,3.358818 7,3.375' + + ' 6.6676654,3.4929791 6.3336971,3.6092802 6.03125,3.78125 6.02349,3.78566 6.007733,3.77681 6,3.78125 5.8811373,3.761018' + + ' 5.8125,3.71875 5.8125,3.71875 l -1.6875,-1 -1.40625,1.4375 0.96875,1.65625 c 0,0 0.065705,0.068637 0.09375,0.1875' + + ' 0.002,0.00849 -0.00169,0.022138 0,0.03125 C 3.6092802,6.3336971 3.4929791,6.6676654 3.375,7 3.3629836,7.0338489' + + ' 3.3239228,7.0596246 3.3125,7.09375 3.125763,7.4756184 2.875,7.5 2.875,7.5 L 1,8 l 0,2 1.875,0.5 c 0,0 0.250763,0.02438' + + ' 0.4375,0.40625 0.017853,0.03651 0.046318,0.04988 0.0625,0.09375 0.1129372,0.318132 0.2124732,0.646641 0.375,0.9375' + + ' -0.00302,0.215512 -0.09375,0.34375 -0.09375,0.34375 L 2.6875,13.9375 4.09375,15.34375 5.78125,14.375 c 0,0' + + ' 0.1229911,-0.09744 0.34375,-0.09375 0.2720511,0.147787 0.5795915,0.23888 0.875,0.34375 0.033849,0.01202 0.059625,0.05108' + + ' 0.09375,0.0625 C 7.4756199,14.874237 7.5,15.125 7.5,15.125 L 8,17 l 2,0 0.5,-1.875 c 0,0 0.02438,-0.250763 0.40625,-0.4375' + + ' 0.03651,-0.01785 0.04988,-0.04632 0.09375,-0.0625 0.332335,-0.117979 0.666303,-0.23428 0.96875,-0.40625 0.177303,0.0173' + + ' 0.28125,0.09375 0.28125,0.09375 l 1.65625,0.96875 1.40625,-1.40625 -0.96875,-1.65625 c 0,0 -0.07645,-0.103947' + + ' -0.09375,-0.28125 0.162527,-0.290859 0.262063,-0.619368 0.375,-0.9375 0.01618,-0.04387 0.04465,-0.05724 0.0625,-0.09375 C' + + ' 14.874237,10.52438 15.125,10.5 15.125,10.5 L 17,10 17,8 15.125,7.5 c 0,0 -0.250763,-0.024382 -0.4375,-0.40625 C' + + ' 14.669647,7.0572406 14.641181,7.0438697 14.625,7 14.55912,6.8144282 14.520616,6.6141566 14.4375,6.4375 c -0.224363,-0.4866' + + ' 0,-0.71875 0,-0.71875 L 15.40625,4.0625 14,2.625 l -1.65625,1 c 0,0 -0.253337,0.1695664 -0.71875,-0.03125 l -0.03125,0 C' + + ' 11.405359,3.5035185 11.198648,3.4455201 11,3.375 10.95613,3.3588185 10.942759,3.3303534 10.90625,3.3125 10.524382,3.125763' + + ' 10.5,2.875 10.5,2.875 L 10,1 8,1 z m 1,5 c 1.656854,0 3,1.3431458 3,3 0,1.656854 -1.343146,3 -3,3 C 7.3431458,12' + + ' 6,10.656854 6,9 6,7.3431458 7.3431458,6 9,6 z').attr({ + 'opacity': 1, + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-signal.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-signal.component.ts new file mode 100644 index 0000000000..7a0b6f5657 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-signal.component.ts @@ -0,0 +1,68 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-signal'}) +export class RaphaelIconSignalDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`M 8.7124971,21.247342 L 23.333334,21.247342 L 16.022915,8.5759512 L 8.7124971,21.247342 z`).attr({ + 'opacity': this.fillOpacity, + 'stroke': this.stroke, + 'strokeWidth': this.strokeWidth, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-timer.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-timer.component.ts new file mode 100644 index 0000000000..22156b044a --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-timer.component.ts @@ -0,0 +1,72 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-timer'}) +export class RaphaelIconTimerDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`M 10 0 C 4.4771525 0 0 4.4771525 0 10 C 0 15.522847 4.4771525 20 10 20 C 15.522847 20 20 15.522847 20 + 10 C 20 4.4771525 15.522847 1.1842379e-15 10 0 z M 9.09375 1.03125 C 9.2292164 1.0174926 9.362825 1.0389311 9.5 1.03125 L 9.5 3.5 + L 10.5 3.5 L 10.5 1.03125 C 15.063526 1.2867831 18.713217 4.9364738 18.96875 9.5 L 16.5 9.5 L 16.5 10.5 L 18.96875 10.5 C 18.713217 + 15.063526 15.063526 18.713217 10.5 18.96875 L 10.5 16.5 L 9.5 16.5 L 9.5 18.96875 C 4.9364738 18.713217 1.2867831 15.063526 1.03125 + 10.5 L 3.5 10.5 L 3.5 9.5 L 1.03125 9.5 C 1.279102 5.0736488 4.7225326 1.4751713 9.09375 1.03125 z M 9.5 5 L 9.5 8.0625 C 8.6373007 + 8.2844627 8 9.0680195 8 10 C 8 11.104569 8.8954305 12 10 12 C 10.931981 12 11.715537 11.362699 11.9375 10.5 L 14 10.5 L 14 9.5 + L 11.9375 9.5 C 11.756642 8.7970599 11.20294 8.2433585 10.5 8.0625 L 10.5 5 L 9.5 5 z`).attr({ + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-user.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-user.component.ts new file mode 100644 index 0000000000..b0ee5a149e --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/icons/raphael-icon-user.component.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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './../models/point'; +import { RaphaelBase } from './../raphael-base'; +import { RaphaelService } from './../raphael.service'; + +@Directive({selector: 'raphael-icon-user'}) +export class RaphaelIconUserDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.position); + } + + public draw(position: Point) { + let path1 = this.paper.path(`m 1,17 16,0 0,-1.7778 -5.333332,-3.5555 0,-1.7778 c 1.244444,0 1.244444,-2.3111 1.244444,-2.3111 + l 0,-3.0222 C 12.555557,0.8221 9.0000001,1.0001 9.0000001,1.0001 c 0,0 -3.5555556,-0.178 -3.9111111,3.5555 l 0,3.0222 c + 0,0 0,2.3111 1.2444443,2.3111 l 0,1.7778 L 1,15.2222 1,17 17,17`).attr({ + 'opacity': 1, + 'stroke': this.stroke, + 'fill': this.fillColors + }); + return path1.transform('T' + position.x + ',' + position.y); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/index.ts new file mode 100644 index 0000000000..fff8b99a57 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/index.ts @@ -0,0 +1,58 @@ +/*! + * @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 { RaphaelCircleDirective } from './raphael-circle.component'; +import { RaphaelRectDirective } from './raphael-rect.component'; +import { RaphaelTextDirective } from './raphael-text.component'; +import { RaphaelFlowArrowDirective } from './raphael-flow-arrow.component'; +import { RaphaelCrossDirective } from './raphael-cross.component'; +import { RaphaelPlusDirective } from './raphael-plus.component'; +import { RaphaelRhombusDirective } from './raphael-rhombus.component'; +import { RaphaelPentagonDirective } from './raphael-pentagon.component'; + +// services +import { RaphaelService } from './raphael.service'; + +// icons +import { RAPHAEL_ICONS_DIRECTIVES } from './icons/index'; + +// primitives +export * from './raphael-circle.component'; +export * from './raphael-rect.component'; +export * from './raphael-text.component'; +export * from './raphael-flow-arrow.component'; +export * from './raphael-cross.component'; +export * from './raphael-plus.component'; +export * from './raphael-rhombus.component'; +export * from './raphael-pentagon.component'; +export * from './icons/index'; + +export const RAPHAEL_DIRECTIVES: any[] = [ + RaphaelCircleDirective, + RaphaelRectDirective, + RaphaelTextDirective, + RaphaelFlowArrowDirective, + RaphaelCrossDirective, + RaphaelPlusDirective, + RaphaelRhombusDirective, + RaphaelPentagonDirective, + RAPHAEL_ICONS_DIRECTIVES +]; + +export const RAPHAEL_PROVIDERS: any[] = [ + RaphaelService +]; diff --git a/ng2-components/ng2-alfresco-viewer/src/assets/PDFJS.mock.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/models/point.ts similarity index 76% rename from ng2-components/ng2-alfresco-viewer/src/assets/PDFJS.mock.ts rename to ng2-components/ng2-activiti-diagrams/src/components/raphael/models/point.ts index e7d54dacdf..58bc45a87e 100644 --- a/ng2-components/ng2-alfresco-viewer/src/assets/PDFJS.mock.ts +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/models/point.ts @@ -15,15 +15,12 @@ * limitations under the License. */ +export class Point { + x: number; + y: number; -export class PDFJSmock { - - getDocument(url: string) { - return new Promise((resolve) => { - resolve({numPages: '10'}); - }); - } - - getPage(numberPage: number) { + constructor(obj?: any) { + this.x = obj && obj.x || 0; + this.y = obj && obj.y || 0; } } diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-base.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-base.ts new file mode 100644 index 0000000000..6e8292d754 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-base.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 { ElementRef } from '@angular/core'; +import { RaphaelService } from './raphael.service'; + +export class RaphaelBase { + + paper: any; + + private element: ElementRef; + + public constructor(element: ElementRef, + private raphaelService: RaphaelService) { + this.element = element; + this.paper = this.raphaelService.getInstance(element); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-circle.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-circle.component.ts new file mode 100644 index 0000000000..566ceb0754 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-circle.component.ts @@ -0,0 +1,64 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './models/point'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-circle'}) +export class RaphaelCircleDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + center: Point; + + @Input() + radius: number; + + @Input() + strokeWidth: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + fillOpacity: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + let opts = {'stroke-width': this.strokeWidth, 'fill': this.fillColors, 'stroke': this.stroke, 'fill-opacity': this.fillOpacity}; + this.draw(this.center, this.radius, opts); + } + + public draw(center: Point, radius: number, opts: any) { + let circle = this.paper.circle(center.x, center.y, radius).attr(opts); + return circle; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-cross.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-cross.component.ts new file mode 100644 index 0000000000..e023868792 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-cross.component.ts @@ -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. + */ + +import { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './models/point'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-cross'}) +export class RaphaelCrossDirective extends RaphaelBase implements OnInit { + @Input() + center: Point; + + @Input() + width: number; + + @Input() + height: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + strokeWidth: any; + + @Input() + fillOpacity: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + let opts = {'stroke-width': this.strokeWidth, 'fill': this.fillColors, 'stroke': this.stroke, 'fill-opacity': this.fillOpacity}; + this.draw(this.center, this.width, this.height, opts); + } + + public draw(center: Point, width: number, height: number, opts?: any) { + let quarterWidth = width / 4; + let quarterHeight = height / 4; + + return this.paper.path( + 'M' + (center.x + quarterWidth + 3) + ' ' + (center.y + quarterHeight + 3) + + 'L' + (center.x + 3 * quarterWidth - 3) + ' ' + (center.y + 3 * quarterHeight - 3) + + 'M' + (center.x + quarterWidth + 3) + ' ' + (center.y + 3 * quarterHeight - 3) + + 'L' + (center.x + 3 * quarterWidth - 3) + ' ' + (center.y + quarterHeight + 3) + ).attr(opts); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-flow-arrow.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-flow-arrow.component.ts new file mode 100644 index 0000000000..3cf8ae5cb3 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-flow-arrow.component.ts @@ -0,0 +1,79 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-flow-arrow'}) +export class RaphaelFlowArrowDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + flow: any; + + @Output() + onError = new EventEmitter(); + + ARROW_WIDTH = 4; + SEQUENCEFLOW_STROKE = 1.5; + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + this.draw(this.flow); + } + + public draw(flow: any) { + let line = this.drawLine(flow); + this.drawArrow(line); + } + + public drawLine(flow: any) { + let polyline = new Polyline(flow.id, flow.waypoints, this.SEQUENCEFLOW_STROKE, this.paper); + polyline.element = this.paper.path(polyline.path); + polyline.element.attr({'stroke-width': this.SEQUENCEFLOW_STROKE}); + polyline.element.attr({'stroke': '#585858'}); + + polyline.element.id = this.flow.id; + + let lastLineIndex = polyline.getLinesCount() - 1; + let line = polyline.getLine(lastLineIndex); + return line; + } + + public drawArrow(line: any) { + let doubleArrowWidth = 2 * this.ARROW_WIDTH; + let width = this.ARROW_WIDTH / 2 + .5; + let arrowHead: any = this.paper.path('M0 0L-' + width + '-' + doubleArrowWidth + 'L' + width + ' -' + doubleArrowWidth + 'z'); + + arrowHead.transform('t' + line.x2 + ',' + line.y2); + let angle = Raphael.deg(line.angle - Math.PI / 2); + arrowHead.transform('...r' + angle + ' 0 0'); + + arrowHead.attr('fill', '#585858'); + + arrowHead.attr('stroke-width', this.SEQUENCEFLOW_STROKE); + arrowHead.attr('stroke', '#585858'); + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-pentagon.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-pentagon.component.ts new file mode 100644 index 0000000000..cdc6a26892 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-pentagon.component.ts @@ -0,0 +1,68 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './models/point'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-pentagon'}) +export class RaphaelPentagonDirective extends RaphaelBase implements OnInit { + @Input() + center: Point; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + strokeWidth: any; + + @Input() + fillOpacity: any; + + @Input() + strokeLinejoin: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + let opts = { + 'stroke-width': this.strokeWidth, + 'fill': this.fillColors, + 'stroke': this.stroke, + 'fill-opacity': this.fillOpacity, + 'stroke-linejoin': 'bevel' + }; + this.draw(this.center, opts); + } + + public draw(center: Point, opts?: any) { + let penta = this.paper.path('M 20.327514,22.344972 L 11.259248,22.344216 L 8.4577203,13.719549' + + ' L 15.794545,8.389969 L 23.130481,13.720774 L 20.327514,22.344972 z').attr(opts); + penta.transform('T' + (center.x + 4) + ',' + (center.y + 4)); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-plus.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-plus.component.ts new file mode 100644 index 0000000000..81db7bd020 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-plus.component.ts @@ -0,0 +1,58 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './models/point'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-plus'}) +export class RaphaelPlusDirective extends RaphaelBase implements OnInit { + @Input() + center: Point; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + strokeWidth: any; + + @Input() + fillOpacity: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + let opts = {'stroke-width': this.strokeWidth, 'fill': this.fillColors, 'stroke': this.stroke, 'fill-opacity': this.fillOpacity}; + this.draw(this.center, opts); + } + + public draw(center: Point, opts?: any) { + let path = this.paper.path('M 6.75,16 L 25.75,16 M 16,6.75 L 16,25.75').attr(opts); + return path.transform('T' + (center.x + 4) + ',' + (center.y + 4)); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-rect.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-rect.component.ts new file mode 100644 index 0000000000..d4b147f914 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-rect.component.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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './models/point'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-rect'}) +export class RaphaelRectDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + leftCorner: Point; + + @Input() + width: number; + + @Input() + height: number; + + @Input() + radius: number = 0; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + strokeWidth: any; + + @Input() + fillOpacity: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + let opts = {'stroke-width': this.strokeWidth, 'fill': this.fillColors, 'stroke': this.stroke, 'fill-opacity': this.fillOpacity}; + this.draw(this.leftCorner, this.width, this.height, this.radius, opts); + } + + public draw(leftCorner: Point, width: number, height: number, radius: number, opts: any) { + return this.paper.rect(leftCorner.x, leftCorner.y, width, height, radius).attr(opts); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-rhombus.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-rhombus.component.ts new file mode 100644 index 0000000000..08e2d7d3fd --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-rhombus.component.ts @@ -0,0 +1,67 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './models/point'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-rhombus'}) +export class RaphaelRhombusDirective extends RaphaelBase implements OnInit { + @Input() + center: Point; + + @Input() + width: number; + + @Input() + height: number; + + @Input() + fillColors: any; + + @Input() + stroke: any; + + @Input() + strokeWidth: any; + + @Input() + fillOpacity: any; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + let opts = {'stroke-width': this.strokeWidth, 'fill': this.fillColors, 'stroke': this.stroke, 'fill-opacity': this.fillOpacity}; + this.draw(this.center, this.width, this.height, opts); + } + + public draw(center: Point, width: number, height: number, opts?: any) { + this.paper.path('M' + center.x + ' ' + (center.y + (height / 2)) + + 'L' + (center.x + (width / 2)) + ' ' + (center.y + height) + + 'L' + (center.x + width) + ' ' + (center.y + (height / 2)) + + 'L' + (center.x + (width / 2)) + ' ' + center.y + 'z' + ).attr(opts); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-text.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-text.component.ts new file mode 100644 index 0000000000..bb177072bb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael-text.component.ts @@ -0,0 +1,64 @@ +/*! + * @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 { Directive, OnInit, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { Point } from './models/point'; +import { RaphaelBase } from './raphael-base'; +import { RaphaelService } from './raphael.service'; + +@Directive({selector: 'raphael-text'}) +export class RaphaelTextDirective extends RaphaelBase implements OnInit { + @Input() + paper: any; + + @Input() + position: Point; + + @Input() + transform: string; + + @Input() + text: string; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef, + raphaelService: RaphaelService) { + super(elementRef, raphaelService); + } + + ngOnInit() { + console.log(this.elementRef); + if (this.text === null || this.text === undefined) { + this.text = ''; + } + this.draw(this.position, this.text); + } + + public draw(position: Point, text: string) { + let textPaper = this.paper.text(position.x, position.y, text).attr({ + 'text-anchor' : 'middle', + 'font-family' : 'Arial', + 'font-size' : '11', + 'fill' : '#373e48' + }); + + textPaper.transform(this.transform); + return textPaper; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael.service.ts b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael.service.ts new file mode 100644 index 0000000000..44da5c3324 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/raphael/raphael.service.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 { Injectable } from '@angular/core'; + +@Injectable() +export class RaphaelService { + + paper: any; + width: number = 300; + height: number = 400 ; + private ctx: any; + + constructor() { + } + + public getInstance(element: any): any { + if (!this.paper) { + this.ctx = element.nativeElement; + this.refresh(); + } + return this.paper; + } + + private refresh(): any { + this.ngOnDestroy(); + this.paper = this.getPaperBuilder(this.ctx); + } + + public getPaperBuilder(ctx: any): any { + if (typeof Raphael === 'undefined') { + throw new Error('ng2-charts configuration issue: Embedding Chart.js lib is mandatory'); + } + let paper = new Raphael(ctx, this.width, this.height); + // paper.setViewBox(0, 0, 583, 344.08374193550003, false); + // paper.renderfix(); + return paper; + } + + private ngOnDestroy(): any { + if (this.paper) { + this.paper.clear(); + this.paper = void 0; + } + } + + public setting(width: number, height: number): void { + this.width = width; + this.height = height; + } + + public reset(): any { + this.ngOnDestroy(); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-event-subprocess.component.html b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-event-subprocess.component.html new file mode 100644 index 0000000000..a3b195375b --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-event-subprocess.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-event-subprocess.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-event-subprocess.component.ts new file mode 100644 index 0000000000..7cf1666c67 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-event-subprocess.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-event-subprocess', + templateUrl: './diagram-event-subprocess.component.html' +}) +export class DiagramEventSubprocessComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + rectLeftCorner: any; + width: any; + height: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: '', radius: 4}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.rectLeftCorner = {x: this.data.x, y: this.data.y}; + this.width = this.data.width; + this.height = this.data.height; + + this.options.fillColors = 'none'; + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.strokeWidth = 1; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-subprocess.component.html b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-subprocess.component.html new file mode 100644 index 0000000000..a3b195375b --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-subprocess.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-subprocess.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-subprocess.component.ts new file mode 100644 index 0000000000..dae3ddfb11 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/structural/diagram-subprocess.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-subprocess', + templateUrl: './diagram-subprocess.component.html' +}) +export class DiagramSubprocessComponent { + @Input() + data: any; + + @Output() + onError = new EventEmitter(); + + rectLeftCorner: any; + width: any; + height: any; + + options: any = {stroke: '', fillColors: '', fillOpacity: '', strokeWidth: '', radius: 4}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.rectLeftCorner = {x: this.data.x, y: this.data.y}; + this.width = this.data.width; + this.height = this.data.height; + + this.options.fillColors = 'none'; + this.options.stroke = this.diagramColorService.getBpmnColor(this.data, DiagramColorService.MAIN_STROKE_COLOR); + this.options.strokeWidth = 1; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/structural/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/structural/index.ts new file mode 100644 index 0000000000..a13ebbc842 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/structural/index.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. + */ + +import { DiagramSubprocessComponent } from './diagram-subprocess.component'; +import { DiagramEventSubprocessComponent } from './diagram-event-subprocess.component'; + +// primitives +export * from './diagram-subprocess.component'; +export * from './diagram-event-subprocess.component'; + +export const DIAGRAM_STRUCTURAL_DIRECTIVES: any[] = [ + DiagramSubprocessComponent, + DiagramEventSubprocessComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lane.component.html b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lane.component.html new file mode 100644 index 0000000000..a4460506bd --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lane.component.html @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lane.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lane.component.ts new file mode 100644 index 0000000000..325b018f63 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lane.component.ts @@ -0,0 +1,54 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-lane', + templateUrl: './diagram-lane.component.html' +}) +export class DiagramLaneComponent { + @Input() + lane: any; + + @Output() + onError = new EventEmitter(); + + rectLeftCorner: any; + width: any; + height: any; + + textPosition: any; + text: string; + textTransform: string; + options: any = {stroke: '#000000', fillColors: 'none', fillOpacity: '', strokeWidth: '1', radius: 0}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.rectLeftCorner = {x: this.lane.x, y: this.lane.y}; + this.width = this.lane.width; + this.height = this.lane.height; + + this.textPosition = {x: this.lane.x + 10, y: this.lane.y + ( this.lane.height / 2 )}; + this.text = this.lane.name; + this.textTransform = 'r270'; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lanes.component.html b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lanes.component.html new file mode 100644 index 0000000000..ef914e2b45 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lanes.component.html @@ -0,0 +1,5 @@ +
+
+ +
+
diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lanes.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lanes.component.ts new file mode 100644 index 0000000000..072175d68c --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-lanes.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-lanes', + templateUrl: './diagram-lanes.component.html' +}) +export class DiagramLanesComponent { + @Input() + lanes: any []; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pool.component.html b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pool.component.html new file mode 100644 index 0000000000..a4460506bd --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pool.component.html @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pool.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pool.component.ts new file mode 100644 index 0000000000..59dd0a0dde --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pool.component.ts @@ -0,0 +1,54 @@ +/*! + * @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, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { DiagramColorService } from '../../services/diagram-color.service'; + +@Component({ + moduleId: module.id, + selector: 'diagram-pool', + templateUrl: './diagram-pool.component.html' +}) +export class DiagramPoolComponent { + @Input() + pool: any; + + @Output() + onError = new EventEmitter(); + + rectLeftCorner: any; + width: any; + height: any; + + textPosition: any; + text: string; + textTransform: string; + options: any = {stroke: '#000000', fillColors: 'none', fillOpacity: '', strokeWidth: '1', radius: 0}; + + constructor(public elementRef: ElementRef, + private diagramColorService: DiagramColorService) {} + + ngOnInit() { + this.rectLeftCorner = {x: this.pool.x, y: this.pool.y}; + this.width = this.pool.width; + this.height = this.pool.height; + + this.textPosition = {x: this.pool.x + 14, y: this.pool.y + ( this.pool.height / 2 )}; + this.text = this.pool.name; + this.textTransform = 'r270'; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pools.component.html b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pools.component.html new file mode 100644 index 0000000000..e963bcdbad --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pools.component.html @@ -0,0 +1,6 @@ +
+
+ + +
+
diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pools.component.ts b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pools.component.ts new file mode 100644 index 0000000000..e54b1f0704 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/diagram-pools.component.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 { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'diagram-pools', + templateUrl: './diagram-pools.component.html' +}) +export class DiagramPoolsComponent { + @Input() + pools: any []; + + @Output() + onError = new EventEmitter(); + + constructor(public elementRef: ElementRef) {} + + ngOnInit() { + + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/index.ts b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/index.ts new file mode 100644 index 0000000000..218e2b88fb --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/components/swimlanes/index.ts @@ -0,0 +1,35 @@ +/*! + * @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 { DiagramPoolsComponent } from './diagram-pools.component'; +import { DiagramPoolComponent } from './diagram-pool.component'; + +import { DiagramLanesComponent } from './diagram-lanes.component'; +import { DiagramLaneComponent } from './diagram-lane.component'; + +// primitives +export * from './diagram-pools.component'; +export * from './diagram-pool.component'; +export * from './diagram-lanes.component'; +export * from './diagram-lane.component'; + +export const DIAGRAM_SWIMLANES_DIRECTIVES: any[] = [ + DiagramPoolsComponent, + DiagramPoolComponent, + DiagramLanesComponent, + DiagramLaneComponent +]; diff --git a/ng2-components/ng2-activiti-diagrams/src/declarations.d.ts b/ng2-components/ng2-activiti-diagrams/src/declarations.d.ts new file mode 100644 index 0000000000..e1b5a9e9fc --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/declarations.d.ts @@ -0,0 +1,19 @@ +/*! + * @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. + */ + +// MDL +declare let componentHandler: any; diff --git a/ng2-components/ng2-activiti-diagrams/src/i18n/en.json b/ng2-components/ng2-activiti-diagrams/src/i18n/en.json new file mode 100644 index 0000000000..1dce1a9c60 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/i18n/en.json @@ -0,0 +1,45 @@ +{ + "ANALYTICS": { + "TTILE": "ANALYTICS", + "MESSAGES": { + "UNKNOWN-WIDGET-TYPE": "UNKNOWN WIDGET TYPE", + "FILL-PARAMETER": "Fill in the parameters to generate your report", + "NO-DATA-FOUND": "No data found" + } + }, + "__KEY_REPORTING": { + "DEFAULT-REPORTS": { + "PROCESS-DEFINITION-OVERVIEW": { + "GENERAL-TABLE-TOTAL-PROCESS-DEFINITIONS": "Total number of process definitions", + "GENERAL-TABLE-TOTAL-PROCESS-INSTANCES": "Total number of process instances", + "GENERAL-TABLE-ACTIVE-PROCESS-INSTANCES": "Total number of active process instances", + "GENERAL-TABLE-COMPLETED-PROCESS-INSTANCES": "Total number of completed process instances" + } + } + }, + "REPORTING": { + "DEFAULT-REPORTS": { + "PROCESS-HEAT-MAP": { + "TYPE-FILTERING": "Include all process steps (Unchecking this, will remove pass through steps like start events, gateways, etc.)?" + }, + "PROCESS-INSTANCES-OVERVIEW": { + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "SLOW-PROC-INST-NUMBER": "How many of the slowest process instances should be displayed?" + }, + "TASK-OVERVIEW": { + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "DATE-RANGE-INTERVAL": "Aggregate dates by" + }, + "TASK-SLA": { + "TASK": "Task", + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "SLA-DURATION": "What is the time this task needs to be completed in to be within the SLA?" + } + }, + "PROCESS-STATUS": "Process status", + "TASK-STATUS": "Task status" + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/i18n/it.json b/ng2-components/ng2-activiti-diagrams/src/i18n/it.json new file mode 100644 index 0000000000..15c05b6832 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/i18n/it.json @@ -0,0 +1,5 @@ +{ + "ANALYTICS": { + "TTILE": "ANALYTICS" + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/models/chart.model.ts b/ng2-components/ng2-activiti-diagrams/src/models/chart.model.ts new file mode 100644 index 0000000000..bba431596e --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/models/chart.model.ts @@ -0,0 +1,185 @@ +/*! + * @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 Chart { + id: string; + type: string; + + constructor(obj?: any) { + this.id = obj && obj.id || null; + if (obj && obj.type) { + this.type = this.convertType(obj.type); + } + } + + private convertType(type: string) { + let chartType = ''; + switch (type) { + case 'pieChart': + chartType = 'pie'; + break; + case 'table': + chartType = 'table'; + break; + case 'line': + chartType = 'line'; + break; + case 'barChart': + chartType = 'bar'; + break; + case 'processDefinitionHeatMap': + chartType = 'HeatMap'; + break; + default: + chartType = 'table'; + break; + } + return chartType; + } +} + +export class LineChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.labels = obj && obj.columnNames.slice(1, obj.columnNames.length); + + obj.rows.forEach((value: any) => { + this.datasets.push({data: value.slice(1, value.length), label: value[0]}); + }); + } +} + +export class BarChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + data: any[] = []; + options: any = { + scales: { + yAxes: [{ + ticks: { + beginAtZero: true, + stepSize: 1 + } + }] + } + }; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + obj.values.forEach((params: any) => { + let dataValue = []; + params.values.forEach((info: any) => { + info.forEach((value: any, index: any) => { + if (index % 2 === 0) { + this.labels.push(value); + } else { + dataValue.push(value); + } + }); + }); + if (dataValue && dataValue.length > 0) { + this.datasets.push({data: dataValue, label: params.key}); + } + }); + } + + hasDatasets() { + return this.datasets && this.datasets.length > 0 ? true : false; + } +} + +export class TableChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.labels = obj && obj.columnNames; + if (obj.rows) { + this.datasets = obj && obj.rows; + } + } + + hasDatasets() { + return this.datasets && this.datasets.length > 0 ? true : false; + } +} + +export class HeatMapChart extends Chart { + avgTimePercentages: string; + avgTimeValues: string; + processDefinitionId: string; + titleKey: string; + totalCountValues: string; + totalCountsPercentages: string; + totalTimePercentages: string; + totalTimeValues: string; + + constructor(obj?: any) { + super(obj); + this.avgTimePercentages = obj && obj.avgTimePercentages || null; + this.avgTimeValues = obj && obj.avgTimeValues || null; + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.totalCountValues = obj && obj.totalCountValues || null; + this.titleKey = obj && obj.titleKey || null; + this.totalCountsPercentages = obj && obj.totalCountsPercentages || null; + this.totalTimePercentages = obj && obj.totalTimePercentages || null; + this.totalTimeValues = obj && obj.totalTimeValues || null; + } +} + +export class PieChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + data: string[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + if (obj.values) { + obj.values.forEach((value: any) => { + this.add(value.key, value.y); + }); + } + } + + add(label: string, data: string) { + this.labels.push(label); + this.data.push(data); + } + + hasData() { + return this.data && this.data.length > 0 ? true : false; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/models/report.model.ts b/ng2-components/ng2-activiti-diagrams/src/models/report.model.ts new file mode 100644 index 0000000000..549f841add --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/models/report.model.ts @@ -0,0 +1,139 @@ +/*! + * @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 report definition. + * + * + * @returns {ReportParametersModel} . + */ +export class ReportParametersModel { + id: number; + name: string; + definition: ReportDefinitionModel; + created: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + if (obj && obj.definition) { + this.definition = new ReportDefinitionModel(JSON.parse(obj.definition)); + } + this.created = obj && obj.created || null; + } + + hasParameters() { + return (this.definition && this.definition.parameters) ? true : false; + } +} + +export class ReportDefinitionModel { + parameters: ReportParameterDetailsModel[] = []; + + constructor(obj?: any) { + obj.parameters.forEach((params: any) => { + let reportParamsModel = new ReportParameterDetailsModel(params); + this.parameters.push(reportParamsModel); + }); + } + + findParam(name: string): ReportParameterDetailsModel { + this.parameters.forEach((param) => { + return param.type === name ? param : null; + }); + return null; + } +} + +/** + * + * This object represent the report parameter definition. + * + * + * @returns {ReportParameterDetailsModel} . + */ +export class ReportParameterDetailsModel { + id: string; + name: string; + nameKey: string; + type: string; + value: string; + options: ParameterValueModel[]; + dependsOn: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + this.nameKey = obj && obj.nameKey || null; + this.type = obj && obj.type || null; + this.value = obj && obj.value || null; + this.options = obj && obj.options || null; + this.dependsOn = obj && obj.dependsOn || null; + } +} + +export class ParameterValueModel { + id: string; + name: string; + version: string; + value: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + this.value = obj && obj.value || null; + this.version = obj && obj.version || null; + } + + get label () { + return this.version ? `${this.name} (v ${this.version}) ` : this.name; + } +} + +export class ReportQuery { + processDefinitionId: string; + status: string; + taskName: string; + typeFiltering: boolean; + dateRange: ReportDateRange; + dateRangeInterval: string; + slowProcessInstanceInteger: number; + duration: number; + + constructor(obj?: any) { + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.status = obj && obj.status || null; + this.taskName = obj && obj.taskName || null; + this.dateRangeInterval = obj && obj.dateRangeInterval || null; + this.typeFiltering = obj && obj.typeFiltering || true; + this.slowProcessInstanceInteger = obj && obj.slowProcessInstanceInteger || 0; + this.duration = obj && obj.duration || 0; + this.dateRange = new ReportDateRange(obj); + } +} + +export class ReportDateRange { + startDate: string; + endDate: string; + + constructor(obj?: any) { + this.startDate = obj && obj.startDate || null; + this.endDate = obj && obj.endDate || null; + } + +} diff --git a/ng2-components/ng2-activiti-diagrams/src/services/diagram-color.service.ts b/ng2-components/ng2-activiti-diagrams/src/services/diagram-color.service.ts new file mode 100644 index 0000000000..4e64dccc6f --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/services/diagram-color.service.ts @@ -0,0 +1,78 @@ +/*! + * @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'; + +@Injectable() +export class DiagramColorService { + + static CURRENT_COLOR = '#017501'; + static COMPLETED_COLOR = '#2632aa'; + static ACTIVITY_STROKE_COLOR = '#bbbbbb'; + static MAIN_STROKE_COLOR = '#585858'; + + static ACTIVITY_FILL_COLOR = '#f9f9f9'; + + static TASK_STROKE = 1; + static TASK_HIGHLIGHT_STROKE = 2; + static CALL_ACTIVITY_STROKE = 2; + + totalColors: any; + + constructor() { + } + + setTotalColors(totalColors) { + this.totalColors = totalColors; + } + + getFillOpacity(): string { + return '0.6'; + } + + getFillColour(key: string) { + if (this.totalColors && this.totalColors.hasOwnProperty(key)) { + let colorPercentage = this.totalColors[key]; + return this.convertColorToHsb(colorPercentage); + } else { + return DiagramColorService.ACTIVITY_FILL_COLOR; + } + } + + getBpmnColor(data, defaultColor) { + if (data.current) { + return DiagramColorService.CURRENT_COLOR; + } else if (data.completed) { + return DiagramColorService.COMPLETED_COLOR; + } else { + return defaultColor; + } + } + + getBpmnStrokeWidth(data) { + if (data.current || data.completed) { + return DiagramColorService.TASK_HIGHLIGHT_STROKE; + } else { + return DiagramColorService.TASK_STROKE; + } + } + + convertColorToHsb(colorPercentage: number): string { + let hue = (120.0 - (colorPercentage * 1.2)) / 360.0; + return 'hsb(' + hue + ', 1, 1)'; + } +} diff --git a/ng2-components/ng2-activiti-diagrams/src/services/diagrams.service.ts b/ng2-components/ng2-activiti-diagrams/src/services/diagrams.service.ts new file mode 100644 index 0000000000..d2d9201b4d --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/src/services/diagrams.service.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. + */ + +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'; + +@Injectable() +export class DiagramsService { + + constructor(private authService: AlfrescoAuthenticationService, + private http: Http, + private alfrescoSettingsService: AlfrescoSettingsService) { + } + + getProcessDefinitionModel(processDefinitionId: string): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/process-definitions/${processDefinitionId}/model-json`; + let options = this.getRequestOptions(); + return this.http + .get(url, options) + .map((res: any) => { + let body = res.json(); + return body; + }).catch(this.handleError); + } + + public getHeaders(): Headers { + return new Headers({ + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': this.authService.getTicketBpm() + }); + } + + public getRequestOptions(param?: any): RequestOptions { + let headers = this.getHeaders(); + return new RequestOptions({headers: headers, withCredentials: true, search: param}); + } + + private handleError(error: Response) { + console.error(error); + return Observable.throw(error.json().error || 'Server error'); + } +} diff --git a/ng2-components/ng2-activiti-diagrams/tsconfig.json b/ng2-components/ng2-activiti-diagrams/tsconfig.json new file mode 100644 index 0000000000..7be35bfec8 --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "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", + "types": ["core-js", "jasmine", "node"] + }, + "exclude": [ + "demo", + "node_modules", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-diagrams/tslint.json b/ng2-components/ng2-activiti-diagrams/tslint.json new file mode 100644 index 0000000000..27e0dd81da --- /dev/null +++ b/ng2-components/ng2-activiti-diagrams/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": true, + "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/.npmignore b/ng2-components/ng2-activiti-form/.npmignore index 16ba3e61e5..c5ca623298 100644 --- a/ng2-components/ng2-activiti-form/.npmignore +++ b/ng2-components/ng2-activiti-form/.npmignore @@ -1,7 +1,6 @@ npm-debug.log .idea -assets/ coverage/ node_modules typings/ diff --git a/ng2-components/ng2-activiti-form/README.md b/ng2-components/ng2-activiti-form/README.md index 203a83f93b..f8c02b3ac8 100644 --- a/ng2-components/ng2-activiti-form/README.md +++ b/ng2-components/ng2-activiti-form/README.md @@ -1,4 +1,4 @@ -# Alfresco Bpm Form component for Angular 2 +# Activiti Form Component for Angular 2

travis
@@ -12,7 +12,7 @@
     <img src= - npm downloads + npm downloads license @@ -33,52 +33,127 @@ ## Prerequisites -Before you start using this development framework, make sure you have installed all required software and done all the +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 -``` +Follow the 3 steps below: -### Dependencies +1. Npm -Add the following dependency to your index.html: + ```sh + npm install ng2-activiti-form --save + ``` -```html - -``` +2. 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) + Include these dependencies in your index.html page: -```sh -npm install --save ng2-translate ng2-alfresco-core -``` + ```html -#### 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 - - - - -``` + + + + + + + + + + + + + + + ``` + +3. SystemJs + + Add the following components to your systemjs.config.js file: + + - moment + - ng2-translate + - ng2-alfresco-core + - alfresco-js-api + - ng2-activiti-form + + Please refer to the following example file: [systemjs.config.js](demo/systemjs + .config.js) . ## Basic usage examples +The component shows a Form from Activiti + +```html + +``` + +Usage example of this component : + +**main.ts** +```ts + +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { ActivitiFormModule } from 'ng2-activiti-form'; + +@Component({ + selector: 'activiti-app-demo', + template: `` +}) + +export class FormDemoComponent { + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = 'http://localhost:9999'; + + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + }, + error => { + console.log(error); + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + ActivitiFormModule.forRoot() + ], + declarations: [FormDemoComponent], + bootstrap: [FormDemoComponent] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); + + +``` + ### Display form instance by task id ```html @@ -149,40 +224,38 @@ 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. | +| `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. | +| `showDebugButton` | boolean | false | Toggle debug options. | +| `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. | +| `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()` | +| `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()` | +| `onError` | Invoked at any error | All `form*` events receive an instance of the `FormModel` as event argument for ease of development: @@ -251,36 +324,11 @@ There are two additional functions that can be of a great value when controlling **Please note that if `event.preventDefault()` is not called then default outcome behaviour will also be executed after your custom code.** -## 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 @@ -292,7 +340,7 @@ npm run build $ npm run build:w ``` -### Running unit tests +## Running unit tests ```sh npm test @@ -312,3 +360,27 @@ before performing unit testing. ```sh npm run coverage ``` + +## Demo + +If you want have a demo of how the component works, please check the demo folder : + +```sh +cd demo +npm install +npm start +``` + +## NPM scripts + +| Command | Description | +| --- | --- | +| npm run build | Build component | +| npm run build:w | Build component and keep watching the changes | +| npm run test | Run unit tests in the console | +| npm run test-browser | Run unit tests in the browser +| npm run coverage | Run unit tests and display code coverage report | + +## License + +[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE) diff --git a/ng2-components/ng2-activiti-form/demo/.gitignore b/ng2-components/ng2-activiti-form/demo/.gitignore new file mode 100644 index 0000000000..25beca4c27 --- /dev/null +++ b/ng2-components/ng2-activiti-form/demo/.gitignore @@ -0,0 +1,6 @@ +node_modules +.idea +coverage +dist +typings +!systemjs.config.js diff --git a/ng2-components/ng2-activiti-form/demo/.npmignore b/ng2-components/ng2-activiti-form/demo/.npmignore new file mode 100644 index 0000000000..c51c008259 --- /dev/null +++ b/ng2-components/ng2-activiti-form/demo/.npmignore @@ -0,0 +1,3 @@ +node_modules +dist +typings \ No newline at end of file diff --git a/ng2-components/ng2-activiti-form/demo/README.md b/ng2-components/ng2-activiti-form/demo/README.md new file mode 100644 index 0000000000..3ff7a9b658 --- /dev/null +++ b/ng2-components/ng2-activiti-form/demo/README.md @@ -0,0 +1,13 @@ +# Activiti Form demo + +Install: + +``` +npm install +``` + +Run the project: + +``` +npm start +``` \ No newline at end of file diff --git a/ng2-components/ng2-activiti-form/demo/index.html b/ng2-components/ng2-activiti-form/demo/index.html new file mode 100644 index 0000000000..e56ebeef76 --- /dev/null +++ b/ng2-components/ng2-activiti-form/demo/index.html @@ -0,0 +1,55 @@ + + + + + + Alfresco Angular 2 Activiti Form - Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ng2-components/ng2-activiti-form/demo/package.json b/ng2-components/ng2-activiti-form/demo/package.json new file mode 100644 index 0000000000..e18283a5ce --- /dev/null +++ b/ng2-components/ng2-activiti-form/demo/package.json @@ -0,0 +1,70 @@ +{ + "name": "ng2-activiti-form-demo", + "description": "Alfresco Activiti Form Component - Demo", + "version": "0.1.0", + "author": "Alfresco Software, Ltd.", + "main": "index.js", + "scripts": { + "clean": "npm install rimraf && rimraf dist node_modules typings dist", + "postinstall": "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 && tsc", + "build:w": "npm run tslint && rimraf dist && tsc -w", + "tsc": "tsc", + "tsc:w": "tsc -w", + "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Mario Romano", + "email": "mario.romano@alfresco.com" + } + ], + "keywords": [ + "ng2", + "angular", + "angular2", + "activiti", + "activiti-form" + ], + "dependencies": { + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "moment": "2.15.1", + "md-date-time-picker": "^2.2.0", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-activiti-form": "^0.4.0" + }, + "devDependencies": { + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", + "rimraf": "2.5.2", + "tslint": "^3.8.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" + } +} diff --git a/ng2-components/ng2-activiti-form/demo/src/main.ts b/ng2-components/ng2-activiti-form/demo/src/main.ts new file mode 100644 index 0000000000..ea35ad17b1 --- /dev/null +++ b/ng2-components/ng2-activiti-form/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 { NgModule, Component, OnInit } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { ActivitiFormModule } from 'ng2-activiti-form'; + +@Component({ + selector: 'alfresco-app-demo', + template: ` +
+
+
+

+

+
+ +
+ + ` +}) + +export class FormDemoComponent implements OnInit { + + taskId: number; + + authenticated: boolean; + + host: string = 'http://localhost:9999'; + + ticket: string; + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = this.host; + settingsService.setProviders('BPM'); + + if (this.authService.getTicketBpm()) { + this.ticket = this.authService.getTicketBpm(); + } + } + + public updateTicket(): void { + localStorage.setItem('ticket-BPM', this.ticket); + } + + public updateHost(): void { + this.settingsService.bpmHost = this.host; + this.login(); + } + + public ngOnInit(): void { + this.login(); + } + + login() { + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + this.ticket = this.authService.getTicketBpm(); + this.authenticated = true; + }, + error => { + console.log(error); + this.authenticated = false; + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + ActivitiFormModule.forRoot() + ], + declarations: [FormDemoComponent], + bootstrap: [FormDemoComponent] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/ng2-components/ng2-activiti-form/demo/systemjs.config.js b/ng2-components/ng2-activiti-form/demo/systemjs.config.js new file mode 100644 index 0000000000..3860ed6679 --- /dev/null +++ b/ng2-components/ng2-activiti-form/demo/systemjs.config.js @@ -0,0 +1,48 @@ +/** + * System configuration for Angular 2 samples + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'moment': 'npm:moment/min/moment.min.js', + 'ng2-translate': 'npm:ng2-translate', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-activiti-form': 'npm:ng2-activiti-form/dist' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-charts': { main: 'ng2-charts.js', defaultExtension: 'js'}, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-form': { main: './index.js', defaultExtension: 'js'} + } + }); +})(this); diff --git a/ng2-components/ng2-activiti-form/demo/tsconfig.json b/ng2-components/ng2-activiti-form/demo/tsconfig.json new file mode 100644 index 0000000000..7be35bfec8 --- /dev/null +++ b/ng2-components/ng2-activiti-form/demo/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "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", + "types": ["core-js", "jasmine", "node"] + }, + "exclude": [ + "demo", + "node_modules", + "dist" + ] +} diff --git a/ng2-components/ng2-activiti-form/demo/tslint.json b/ng2-components/ng2-activiti-form/demo/tslint.json new file mode 100644 index 0000000000..8c9703b9de --- /dev/null +++ b/ng2-components/ng2-activiti-form/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, + 180 + ], + "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": true, + "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-form/index.ts b/ng2-components/ng2-activiti-form/index.ts index 093ae1f90c..519cc0e2c3 100644 --- a/ng2-components/ng2-activiti-form/index.ts +++ b/ng2-components/ng2-activiti-form/index.ts @@ -15,19 +15,62 @@ * limitations under the License. */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CoreModule } from 'ng2-alfresco-core'; + +import { ActivitiForm } from './src/components/activiti-form.component'; +import { ActivitiStartForm } from './src/components/activiti-start-form.component'; import { FormService } from './src/services/form.service'; import { EcmModelService } from './src/services/ecm-model.service'; import { NodeService } from './src/services/node.service'; +import { WidgetVisibilityService } from './src/services/widget-visibility.service'; +import { ActivitiAlfrescoContentService } from './src/services/activiti-alfresco.service'; +import { HttpModule } from '@angular/http'; +import { WIDGET_DIRECTIVES } from './src/components/widgets/index'; export * from './src/components/activiti-form.component'; +export * from './src/components/activiti-start-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 ACTIVITI_FORM_DIRECTIVES: any[] = [ + ActivitiForm, + ActivitiStartForm, + ...WIDGET_DIRECTIVES +]; -export const ATIVITI_FORM_PROVIDERS: [any] = [ +export const ACTIVITI_FORM_PROVIDERS: any[] = [ FormService, EcmModelService, - NodeService + NodeService, + WidgetVisibilityService, + ActivitiAlfrescoContentService ]; + +@NgModule({ + imports: [ + CoreModule, + HttpModule + ], + declarations: [ + ...ACTIVITI_FORM_DIRECTIVES + ], + providers: [ + ...ACTIVITI_FORM_PROVIDERS + ], + exports: [ + ...ACTIVITI_FORM_DIRECTIVES + ] +}) +export class ActivitiFormModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: ActivitiFormModule, + providers: [ + ...ACTIVITI_FORM_PROVIDERS + ] + }; + } +} diff --git a/ng2-components/ng2-activiti-form/karma-test-shim.js b/ng2-components/ng2-activiti-form/karma-test-shim.js index 2d378ec947..a58f01a39b 100644 --- a/ng2-components/ng2-activiti-form/karma-test-shim.js +++ b/ng2-components/ng2-activiti-form/karma-test-shim.js @@ -5,103 +5,100 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; __karma__.loaded = function() {}; +var builtPath = '/base/dist/'; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +var paths = { + // paths serve as alias + 'npm:': 'base/node_modules/' +}; + 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' + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // testing + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist' }; var packages = { 'app': { main: 'main.js', defaultExtension: 'js' }, - 'rxjs': { defaultExtension: 'js' }, - 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, - 'ng2-translate': { defaultExtension: 'js' } -}; + 'rxjs': { 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, {}) + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'} }; var config = { + paths: paths, 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') - ]) - }) +System.import('@angular/core/testing') + .then(initTestBed) + .then(initTesting); + +function initTestBed(){ + 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); + var coreTesting = providers[0]; + var browserTesting = providers[1]; + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); }) - .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') +// Import all spec files and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { return System.import(moduleName); - }); + }) + ) + .then(__karma__.start, __karma__.error); } diff --git a/ng2-components/ng2-activiti-form/karma.conf.js b/ng2-components/ng2-activiti-form/karma.conf.js index 40bb7d31ca..1b8835cd42 100644 --- a/ng2-components/ng2-activiti-form/karma.conf.js +++ b/ng2-components/ng2-activiti-form/karma.conf.js @@ -7,25 +7,48 @@ module.exports = function (config) { 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}, + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', - {pattern: 'karma-test-shim.js', included: true, watched: true}, + // Polyfills + 'node_modules/core-js/client/shim.js', + 'node_modules/reflect-metadata/Reflect.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, + { pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, + + // Paths loaded via module imports: + // Angular itself + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, + + 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + 'node_modules/moment/min/moment.min.js', + 'node_modules/md-date-time-picker/dist/js/mdDateTimePicker.min.js', + 'node_modules/md-date-time-picker/dist/js/draggabilly.pkgd.min.js', + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/ng2-translate/**/*.js.map', included: false, watched: false}, + + 'karma-test-shim.js', // 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}, + // ng2-components + { pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false }, + // 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} @@ -76,10 +99,11 @@ module.exports = function (config) { // 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'] + 'dist/**/!(*spec|index|*mock|*model).js': 'coverage' }, coverageReporter: { + includeAllSources: true, dir: 'coverage/', subdir: 'report', reporters: [ diff --git a/ng2-components/ng2-activiti-form/package.json b/ng2-components/ng2-activiti-form/package.json index d0594e8c52..f764072bce 100644 --- a/ng2-components/ng2-activiti-form/package.json +++ b/ng2-components/ng2-activiti-form/package.json @@ -1,13 +1,10 @@ { "name": "ng2-activiti-form", "description": "Alfresco Activiti Form Component for Angular 2", - "version": "0.3.0", + "version": "0.4.0", "author": "Alfresco Software, Ltd.", "scripts": { - "postinstall": "typings install", - "clean": "rimraf dist node_modules typings", - "typings": "typings install", - "server": "wsrv -o -p 9875", + "clean": "npm install rimraf && rimraf dist node_modules typings", "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\"", @@ -48,51 +45,53 @@ "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", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", "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" + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "moment": "2.15.1", + "md-date-time-picker": "^2.2.0", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0" }, "devDependencies": { - "concurrently": "2.1.0", - "coveralls": "2.11.9", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", "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" + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" }, "license-check-config": { "src": [ diff --git a/ng2-components/ng2-activiti-form/src/assets/translation.service.mock.ts b/ng2-components/ng2-activiti-form/src/assets/translation.service.mock.ts new file mode 100644 index 0000000000..e0aa69addb --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/assets/translation.service.mock.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. + */ + +import { Observable } from 'rxjs/Rx'; + +export class TranslationMock { + + get(key: string|Array, interpolateParams?: Object): Observable { + return Observable.of(key); + } + + addTranslationFolder() { + } +} 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 index 6eb9bd0de9..65aca84d10 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.css +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.css @@ -1,6 +1,7 @@ .activiti-form-container { width: 100%; min-height: 100px; + overflow: visible; } .activiti-form-container > .mdl-card__media { 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 index 7bb2551e8a..ae6aaaf3c9 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.html @@ -4,23 +4,43 @@
-
-

{{form.taskName}}

+
+ {{ form.isValid ? 'event_available' : 'event_busy' }} +

{{form.taskName}}

- +
- +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ UNKNOWN WIDGET TYPE: {{field.type}} +
+
+
@@ -38,13 +58,15 @@ For debugging and data visualisation purposes, will be removed during future revisions --> -
+
- +
+ +

Values

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 index 133206d5b6..14ee3e96aa 100644 --- 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 @@ -15,14 +15,13 @@ * 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 { FormModel, FormOutcomeModel, FormFieldModel, FormOutcomeEvent, FormFieldTypes } from './widgets/index'; import { FormService } from './../services/form.service'; import { WidgetVisibilityService } from './../services/widget-visibility.service'; -import { ContainerWidget } from './widgets/container/container.widget'; +import { NodeService } from './../services/node.service'; describe('ActivitiForm', () => { @@ -30,18 +29,20 @@ describe('ActivitiForm', () => { let formService: FormService; let formComponent: ActivitiForm; let visibilityService: WidgetVisibilityService; + let nodeService: NodeService; beforeEach(() => { componentHandler = jasmine.createSpyObj('componentHandler', [ 'upgradeAllRegistered' ]); visibilityService = jasmine.createSpyObj('WidgetVisibilityService', [ - 'updateVisibilityForForm', 'getTaskProcessVariableModelsForTask' + 'refreshVisibility', 'getTaskProcessVariable' ]); window['componentHandler'] = componentHandler; formService = new FormService(null, null); - formComponent = new ActivitiForm(formService, visibilityService, null, null, null); + nodeService = new NodeService(null); + formComponent = new ActivitiForm(formService, visibilityService, null, nodeService); }); it('should upgrade MDL content on view checked', () => { @@ -98,25 +99,24 @@ describe('ActivitiForm', () => { }); it('should not enable outcome button when model missing', () => { - expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy(); + expect(formComponent.isOutcomeButtonVisible(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(); + expect(formComponent.isOutcomeButtonVisible(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(); + expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy(); formComponent.showSaveButton = false; - expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); + expect(formComponent.isOutcomeButtonVisible(outcome)).toBeFalsy(); }); it('should allow controlling [save] button visibility', () => { @@ -124,10 +124,10 @@ describe('ActivitiForm', () => { let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION }); formComponent.showCompleteButton = true; - expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy(); + expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy(); formComponent.showCompleteButton = false; - expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); + expect(formComponent.isOutcomeButtonVisible(outcome)).toBeFalsy(); }); it('should load form on refresh', () => { @@ -145,7 +145,7 @@ describe('ActivitiForm', () => { formComponent.loadForm(); expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId); - expect(visibilityService.getTaskProcessVariableModelsForTask).toHaveBeenCalledWith(taskId); + expect(visibilityService.getTaskProcessVariable).toHaveBeenCalledWith(taskId); }); it('should get form definition by form id on load', () => { @@ -554,7 +554,7 @@ describe('ActivitiForm', () => { let form = formComponent.parseForm({ id: '', fields: [ - { id: 'field1' } + { id: 'field1', type: FormFieldTypes.CONTAINER } ] }); @@ -571,6 +571,7 @@ describe('ActivitiForm', () => { 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(); @@ -581,6 +582,7 @@ describe('ActivitiForm', () => { expect(formComponent.checkVisibility).toHaveBeenCalledWith(fakeField); }); + */ it('should prevent default outcome execution', () => { @@ -623,15 +625,95 @@ describe('ActivitiForm', () => { it('should check visibility only if field with form provided', () => { formComponent.checkVisibility(null); - expect(visibilityService.updateVisibilityForForm).not.toHaveBeenCalled(); + expect(visibilityService.refreshVisibility).not.toHaveBeenCalled(); let field = new FormFieldModel(null); formComponent.checkVisibility(field); - expect(visibilityService.updateVisibilityForForm).not.toHaveBeenCalled(); + expect(visibilityService.refreshVisibility).not.toHaveBeenCalled(); field = new FormFieldModel(new FormModel()); formComponent.checkVisibility(field); - expect(visibilityService.updateVisibilityForForm).toHaveBeenCalledWith(field.form); + expect(visibilityService.refreshVisibility).toHaveBeenCalledWith(field.form); + }); + + it('should load form for ecm node', () => { + let metadata = {}; + spyOn(nodeService, 'getNodeMetadata').and.returnValue( + Observable.create(observer => { + observer.next({ metadata: metadata }); + observer.complete(); + }) + ); + spyOn(formComponent, 'loadFormFromActiviti').and.stub(); + + const nodeId = ''; + formComponent.nodeId = nodeId; + formComponent.ngOnInit(); + + expect(nodeService.getNodeMetadata).toHaveBeenCalledWith(nodeId); + expect(formComponent.loadFormFromActiviti).toHaveBeenCalled(); + expect(formComponent.data).toBe(metadata); + }); + + it('should disable outcome buttons for readonly form', () => { + let formModel = new FormModel(); + formModel.readOnly = true; + formComponent.form = formModel; + + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom' + }); + + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); + }); + + it('should require outcome to eval button state', () => { + formComponent.form = new FormModel(); + expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy(); + }); + + it('should always enable save outcome for writeable form', () => { + let formModel = new FormModel(); + let field = new FormFieldModel(formModel, { + type: 'text', + value: null, + required: true + }); + + formComponent.form = formModel; + formModel.onFormFieldChanged(field); + + expect(formModel.isValid).toBeFalsy(); + + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.SAVE_OUTCOME_ID, + name: FormOutcomeModel.SAVE_ACTION + }); + + formComponent.readOnly = true; + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy(); + }); + + it('should disable oucome buttons for invalid form', () => { + let formModel = new FormModel(); + let field = new FormFieldModel(formModel, { + type: 'text', + value: null, + required: true + }); + + formComponent.form = formModel; + formModel.onFormFieldChanged(field); + + expect(formModel.isValid).toBeFalsy(); + + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom' + }); + + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); }); }); 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 index d763eb0128..e25ee1e82c 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts @@ -23,18 +23,11 @@ import { 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'; /** @@ -60,6 +53,8 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic * * {showRefreshButton} boolean - to hide the refresh button of the form pass false, default true; * + * {showDebugButton} boolean - to show the debug options, default false; + * * {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; @@ -78,12 +73,10 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic * @returns {ActivitiForm} . */ @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'activiti-form', templateUrl: './activiti-form.component.html', - styleUrls: ['./activiti-form.component.css'], - directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget, TabsWidget], - providers: [EcmModelService, FormService, WidgetVisibilityService, NodeService] + styleUrls: ['./activiti-form.component.css'] }) export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { @@ -124,6 +117,9 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { @Input() showSaveButton: boolean = true; + @Input() + showDebugButton: boolean = true; + @Input() readOnly: boolean = false; @@ -142,13 +138,15 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { @Output() executeOutcome: EventEmitter = new EventEmitter(); + @Output() + onError: EventEmitter = new EventEmitter(); + form: FormModel; debugMode: boolean = false; - constructor(private formService: FormService, + constructor(protected formService: FormService, private visibilityService: WidgetVisibilityService, - private authService: AlfrescoAuthenticationService, private ecmModelService: EcmModelService, private nodeService: NodeService) { } @@ -167,6 +165,21 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { } isOutcomeButtonEnabled(outcome: FormOutcomeModel): boolean { + if (this.form.readOnly) { + return false; + } + + if (outcome) { + // Make 'Save' button always available + if (outcome.name === FormOutcomeModel.SAVE_ACTION) { + return true; + } + return this.form.isValid; + } + return false; + } + + isOutcomeButtonVisible(outcome: FormOutcomeModel): boolean { if (outcome && outcome.name) { if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) { return this.showCompleteButton; @@ -181,7 +194,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { ngOnInit() { if (this.nodeId) { - this.loadActivitiFormForEcmNode(); + this.loadFormForEcmNode(); } else { this.loadForm(); } @@ -264,7 +277,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { loadForm() { if (this.taskId) { this.getFormByTaskId(this.taskId); - this.visibilityService.getTaskProcessVariableModelsForTask(this.taskId); + this.visibilityService.getTaskProcessVariable(this.taskId); return; } @@ -297,7 +310,9 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.form = new FormModel(form, data, this.readOnly); this.formLoaded.emit(this.form); }, - this.handleError + (error) => { + this.handleError(error); + } ); } @@ -310,7 +325,9 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.form = this.parseForm(form); this.formLoaded.emit(this.form); }, - this.handleError + (error) => { + this.handleError(error); + } ); } @@ -325,10 +342,14 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.form = this.parseForm(form); this.formLoaded.emit(this.form); }, - this.handleError + (error) => { + this.handleError(error); + } ); }, - this.handleError + (error) => { + this.handleError(error); + } ); } @@ -341,7 +362,9 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.formSaved.emit(this.form); this.storeFormAsMetadata(); }, - this.handleError + (error) => { + this.handleError(error); + } ); } } @@ -355,13 +378,16 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.formCompleted.emit(this.form); this.storeFormAsMetadata(); }, - this.handleError + (error) => { + this.handleError(error); + } ); } } handleError(err: any): any { console.log(err); + this.onError.emit(err); } parseForm(json: any): FormModel { @@ -388,11 +414,11 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { checkVisibility(field: FormFieldModel) { if (field && field.form) { - this.visibilityService.updateVisibilityForForm(field.form); + this.visibilityService.refreshVisibility(field.form); } } - private loadActivitiFormForEcmNode(): void { + private loadFormForEcmNode(): void { this.nodeService.getNodeMetadata(this.nodeId).subscribe(data => { this.data = data.metadata; this.loadFormFromActiviti(data.nodeType); @@ -400,18 +426,20 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.handleError); } - public loadFormFromActiviti(nodeType: string): any { + loadFormFromActiviti(nodeType: string): any { this.formService.searchFrom(nodeType).subscribe( form => { if (!form) { - this.formService.createFormFromNodeType(nodeType).subscribe(formMetadata => { + this.formService.createFormFromANode(nodeType).subscribe(formMetadata => { this.loadFormFromFormId(formMetadata.id); }); } else { this.loadFormFromFormId(form.id); } }, - this.handleError + (error) => { + this.handleError(error); + } ); } @@ -424,7 +452,10 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { 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 + }, + (error) => { + this.handleError(error); + } ); } } diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.html b/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.html new file mode 100644 index 0000000000..2dd449b9b6 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.html @@ -0,0 +1,53 @@ +
+
+
+
+ {{ form.isValid ? 'event_available' : 'event_busy' }} +

{{form.taskName}}

+
+
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ UNKNOWN WIDGET TYPE: {{field.type}} +
+
+
+
+
+
+ +
+
+ +
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.spec.ts b/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.spec.ts new file mode 100644 index 0000000000..46d20208d6 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.spec.ts @@ -0,0 +1,134 @@ +/*! + * @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 { SimpleChange } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { Observable } from 'rxjs/Rx'; + +import { ActivitiStartForm } from './activiti-start-form.component'; +import { WIDGET_DIRECTIVES } from './widgets/index'; +import { FormService } from './../services/form.service'; +import { EcmModelService } from './../services/ecm-model.service'; +import { WidgetVisibilityService } from './../services/widget-visibility.service'; +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; +import { TranslationMock } from './../assets/translation.service.mock'; + +describe('ActivitiStartForm', () => { + + let componentHandler: any; + let formService: FormService; + let component: ActivitiStartForm; + let fixture: ComponentFixture; + let getStartFormSpy: jasmine.Spy; + + const exampleId1 = 'my:process1'; + const exampleId2 = 'my:process2'; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ CoreModule ], + declarations: [ + ActivitiStartForm, + ...WIDGET_DIRECTIVES + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + EcmModelService, + FormService, + WidgetVisibilityService + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiStartForm); + component = fixture.componentInstance; + formService = fixture.debugElement.injector.get(FormService); + + getStartFormSpy = spyOn(formService, 'getStartFormDefinition').and.returnValue(Observable.of({ + processDefinitionName: 'my:process' + })); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should load start form on init if processDefinitionId defined', () => { + component.processDefinitionId = exampleId1; + component.ngOnInit(); + expect(formService.getStartFormDefinition).toHaveBeenCalled(); + }); + + it('should load not start form on init if no processDefinitionId defined', () => { + component.ngOnInit(); + expect(formService.getStartFormDefinition).not.toHaveBeenCalled(); + }); + + it('should load start form when processDefinitionId changed', () => { + component.processDefinitionId = exampleId1; + component.ngOnChanges({processDefinitionId: new SimpleChange(exampleId1, exampleId2)}); + expect(formService.getStartFormDefinition).toHaveBeenCalled(); + }); + + it('should not load start form when changes notified but no change to processDefinitionId', () => { + component.processDefinitionId = exampleId1; + component.ngOnChanges({otherProp: new SimpleChange(exampleId1, exampleId2)}); + expect(formService.getStartFormDefinition).not.toHaveBeenCalled(); + }); + + it('should consume errors encountered when loading start form', () => { + getStartFormSpy.and.returnValue(Observable.throw({})); + component.processDefinitionId = exampleId1; + component.ngOnInit(); + }); + + it('should not show outcome buttons by default', () => { + getStartFormSpy.and.returnValue(Observable.of({ + id: '1', + processDefinitionName: 'my:process', + outcomes: [{ + id: 'approve', + name: 'Approve' + }] + })); + component.processDefinitionId = exampleId1; + component.ngOnInit(); + fixture.detectChanges(); + expect(component.outcomesContainer).not.toBeTruthy(); + }); + + it('should show outcome buttons if showOutcomeButtons is true', () => { + getStartFormSpy.and.returnValue(Observable.of({ + id: '1', + processDefinitionName: 'my:process', + outcomes: [{ + id: 'approve', + name: 'Approve' + }] + })); + component.processDefinitionId = exampleId1; + component.showOutcomeButtons = true; + component.ngOnInit(); + fixture.detectChanges(); + expect(component.outcomesContainer).toBeTruthy(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.ts b/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.ts new file mode 100644 index 0000000000..ae5cedbf9e --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/activiti-start-form.component.ts @@ -0,0 +1,148 @@ +/*! + * @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, + ViewChild, + ElementRef +} from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { ActivitiForm } from './activiti-form.component'; +import { FormService } from './../services/form.service'; +import { WidgetVisibilityService } from './../services/widget-visibility.service'; + +/** + * Displays the start form for a named process definition, which can be used to retrieve values to start a new process. + * + * After the form has been completed the form values are available from the attribute component.form.values and + * component.form.isValid (boolean) can be used to check the if the form is valid or not. Both of these properties are + * updated as the user types into the form. + * + * @Input + * {processDefinitionId} string: The process definition ID + * {showOutcomeButtons} boolean: Whether form outcome buttons should be shown, as yet these don't do anything so this + * is false by default + * @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: module.id, + selector: 'activiti-start-form', + templateUrl: './activiti-start-form.component.html', + styleUrls: ['./activiti-form.component.css'] +}) +export class ActivitiStartForm extends ActivitiForm implements OnInit, AfterViewChecked, OnChanges { + + @Input() + processDefinitionId: string; + + @Input() + processId: string; + + @Input() + showOutcomeButtons: boolean = false; + + @Input() + showRefreshButton: boolean = true; + + @ViewChild('outcomesContainer', {}) + outcomesContainer: ElementRef = null; + + constructor(private translate: AlfrescoTranslationService, + formService: FormService, + visibilityService: WidgetVisibilityService) { + super(formService, visibilityService, null, null); + } + + ngOnInit() { + if (this.processId) { + this.loadStartForm(this.processId); + }else { + this.loadForm(); + } + + if (this.translate) { + this.translate.addTranslationFolder('node_modules/ng2-activiti-form/src'); + } + } + + ngOnChanges(changes: SimpleChanges) { + let processDefinitionId = changes['processDefinitionId']; + if (processDefinitionId && processDefinitionId.currentValue) { + this.getStartFormDefinition(processDefinitionId.currentValue); + return; + } + + let processId = changes['processId']; + if (processId && processId.currentValue) { + this.loadStartForm(processId.currentValue); + return; + } + } + + loadForm() { + if (this.processDefinitionId) { + this.getStartFormDefinition(this.processDefinitionId); + return; + } + } + + loadStartForm(processId: string) { + this.formService + .getStartFormInstance(processId) + .subscribe( + form => { + this.formName = form.name; + form.processDefinitionId = this.processDefinitionId; + this.form = this.parseForm(form); + // this.form.processDefinitionId = this.processDefinitionId; + this.formLoaded.emit(this.form); + }, + (error) => { + this.handleError(error); + } + ); + } + + getStartFormDefinition(processId: string) { + this.formService + .getStartFormDefinition(processId) + .subscribe( + form => { + this.formName = form.processDefinitionName; + this.form = this.parseForm(form); + this.formLoaded.emit(this.form); + }, + (error) => { + this.handleError(error); + } + ); + } + + saveTaskForm() { + } + + completeTaskForm(outcome?: string) { + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/.gitignore b/ng2-components/ng2-activiti-form/src/components/widgets/.gitignore deleted file mode 100644 index 3a6691d8d4..0000000000 --- a/ng2-components/ng2-activiti-form/src/components/widgets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/widget.model.ts diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.css new file mode 100644 index 0000000000..1162c3ac64 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.css @@ -0,0 +1,20 @@ +.amount-widget { + width: 100%; +} + + +.amount-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.amount-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.amount-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.amount-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.html new file mode 100644 index 0000000000..773504db8f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.html @@ -0,0 +1,13 @@ +
+ + + {{field.validationSummary}} +
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.spec.ts new file mode 100644 index 0000000000..75eda10f8e --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.spec.ts @@ -0,0 +1,45 @@ +/*! + * @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 { AmountWidget } from './amount.widget'; +import { FormFieldModel } from './../core/form-field.model'; + +describe('AmountWidget', () => { + + let widget: AmountWidget; + + beforeEach(() => { + widget = new AmountWidget(null); + }); + + it('should setup currentcy from field', () => { + const currency = 'UAH'; + widget.field = new FormFieldModel(null, { + currency: currency + }); + + widget.ngOnInit(); + expect(widget.currency).toBe(currency); + }); + + it('should setup default currency', () => { + widget.field = null; + widget.ngOnInit(); + expect(widget.currency).toBe(AmountWidget.DEFAULT_CURRENCY); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.ts new file mode 100644 index 0000000000..93086e9006 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.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 { Component, ElementRef, OnInit } from '@angular/core'; +import { TextFieldWidgetComponent } from './../textfield-widget.component'; + +@Component({ + moduleId: module.id, + selector: 'amount-widget', + templateUrl: './amount.widget.html', + styleUrls: ['./amount.widget.css'] +}) +export class AmountWidget extends TextFieldWidgetComponent implements OnInit { + + static DEFAULT_CURRENCY: string = '$'; + + currency: string = AmountWidget.DEFAULT_CURRENCY; + + constructor(elementRef: ElementRef) { + super(elementRef); + } + + ngOnInit() { + if (this.field && this.field.currency) { + this.currency = this.field.currency; + } + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.css new file mode 100644 index 0000000000..8bb37584c6 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.css @@ -0,0 +1,15 @@ +.attach-widget { + width:100% +} + +.attach-widget__icon { + float: left; +} + +.attach-widget__file { + margin-top: 4px; +} + +.attach-widget__reset { + margin-top: 4px; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.html new file mode 100644 index 0000000000..e79e98dbc7 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.html @@ -0,0 +1,33 @@ +
+ + +
+ {{getLinkedFileName()}} + + +
+
+ + +

Select content

+
+
+ +
+ diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.spec.ts new file mode 100644 index 0000000000..1f31e743da --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.spec.ts @@ -0,0 +1,293 @@ +/*! + * @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 { AttachWidget } from './attach.widget'; +import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; +import { ExternalContent } from '../core/external-content'; +import { ExternalContentLink } from '../core/external-content-link'; + +describe('AttachWidget', () => { + + let widget: AttachWidget; + let contentService: ActivitiAlfrescoContentService; + let dialogPolyfill: any; + + beforeEach(() => { + contentService = new ActivitiAlfrescoContentService(null); + widget = new AttachWidget(contentService); + + dialogPolyfill = { + registerDialog(obj: any) { + obj.showModal = function () {}; + } + }; + window['dialogPolyfill'] = dialogPolyfill; + }); + + it('should require field value to check file', () => { + widget.hasFile = false; + widget.field = null; + widget.ngOnInit(); + expect(widget.hasFile).toBeFalsy(); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: null + }); + widget.ngOnInit(); + expect(widget.hasFile).toBeFalsy(); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [{ name: 'file' }] + }); + widget.ngOnInit(); + expect(widget.hasFile).toBeTruthy(); + }); + + it('should setup with form field', () => { + let nodes = [{}]; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next(nodes); + observer.complete(); + }) + ); + + let config = { + siteId: '', + site: '', + pathId: '', + accountId: '' + }; + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + params: { + fileSource: { + selectedFolder: config + } + } + }); + widget.ngOnInit(); + + expect(widget.selectedFolderSiteId).toBe(config.siteId); + expect(widget.selectedFolderSiteName).toBe(config.site); + expect(widget.selectedFolderPathId).toBe(config.pathId); + expect(widget.selectedFolderAccountId).toBe(config.accountId); + expect(widget.selectedFolderNodes).toEqual(nodes); + }); + + it('should link file on select', () => { + let link = {}; + spyOn(contentService, 'linkAlfrescoNode').and.returnValue( + Observable.create(observer => { + observer.next(link); + observer.complete(); + }) + ); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD + }); + widget.ngOnInit(); + + let node = {}; + widget.selectFile(node, null); + + expect(contentService.linkAlfrescoNode).toHaveBeenCalled(); + expect(widget.selectedFile).toBe(node); + expect(widget.field.value).toEqual([link]); + expect(widget.field.json.value).toEqual([link]); + }); + + it('should reset', () => { + widget.hasFile = true; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [{ name: 'filename' }] + }); + + widget.reset(); + expect(widget.hasFile).toBeFalsy(); + expect(widget.field.value).toBeNull(); + expect(widget.field.json.value).toBeNull(); + }); + + it('should close dialog on cancel', () => { + let closed = false; + widget.dialog = { + nativeElement: { + close: function() { + closed = true; + } + } + }; + widget.cancel(); + expect(closed).toBeTruthy(); + }); + + it('should show modal dialog', () => { + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next([]); + observer.complete(); + }) + ); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + params: { + fileSource: { + selectedFolder: {} + } + } + }); + + let modalShown = false; + widget.dialog = { + nativeElement: { + showModal: function() { + modalShown = true; + } + } + }; + + widget.showDialog(); + expect(modalShown).toBeTruthy(); + }); + + it('should select folder and load nodes', () => { + let nodes = [{}]; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next(nodes); + observer.complete(); + }) + ); + + let node = { id: '' }; + widget.selectFolder(node, null); + + expect(widget.selectedFolderPathId).toBe(node.id); + expect(widget.selectedFolderNodes).toEqual(nodes); + }); + + it('should get linked file name via local variable', () => { + widget.fileName = ''; + widget.selectedFile = null; + widget.field = null; + expect(widget.getLinkedFileName()).toBe(widget.fileName); + }); + + it('should get linked file name via selected file', () => { + widget.fileName = null; + widget.selectedFile = { title: '' }; + widget.field = null; + expect(widget.getLinkedFileName()).toBe(widget.selectedFile.title); + }); + + it('should get linked file name via form field', () => { + widget.fileName = null; + widget.selectedFile = null; + + let name = '<file>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [{ name: name }] + }); + + expect(widget.getLinkedFileName()).toBe(name); + }); + + it('should require form field to setup file browser', () => { + widget.field = null; + widget.setupFileBrowser(); + + expect(widget.selectedFolderPathId).toBeUndefined(); + expect(widget.selectedFolderAccountId).toBeUndefined(); + + const pathId = '<pathId>'; + const accountId = '<accountId>'; + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + params: { + fileSource: { + selectedFolder: { + pathId: pathId, + accountId: accountId + } + } + } + }); + widget.setupFileBrowser(); + expect(widget.selectedFolderPathId).toBe(pathId); + expect(widget.selectedFolderAccountId).toBe(accountId); + }); + + it('should get external content nodes', () => { + let nodes = [{}]; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next(nodes); + observer.complete(); + }) + ); + + const accountId = '<accountId>'; + const pathId = '<pathId>'; + widget.selectedFolderAccountId = accountId; + widget.selectedFolderPathId = pathId; + widget.getExternalContentNodes(); + + expect(contentService.getAlfrescoNodes).toHaveBeenCalledWith(accountId, pathId); + expect(widget.selectedFolderNodes).toEqual(nodes); + }); + + it('should handle error', () => { + let error = 'error'; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.throw(error) + ); + + spyOn(console, 'log').and.stub(); + widget.getExternalContentNodes(); + expect(console.log).toHaveBeenCalledWith(error); + }); + + it('should register dialog via polyfill', () => { + widget.dialog = { + nativeElement: {} + }; + spyOn(dialogPolyfill, 'registerDialog').and.callThrough(); + spyOn(widget, 'setupFileBrowser').and.stub(); + spyOn(widget, 'getExternalContentNodes').and.stub(); + widget.showDialog(); + expect(dialogPolyfill.registerDialog).toHaveBeenCalledWith(widget.dialog.nativeElement); + }); + + it('should require configured dialog to show modal', () => { + widget.dialog = null; + spyOn(widget, 'setupFileBrowser').and.stub(); + spyOn(widget, 'getExternalContentNodes').and.stub(); + expect(widget.showDialog()).toBeFalsy(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.ts new file mode 100644 index 0000000000..2c77567c02 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.ts @@ -0,0 +1,155 @@ +/*! + * @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, ViewChild } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; +import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service'; +import { ExternalContent } from '../core/external-content'; +import { ExternalContentLink } from '../core/external-content-link'; +import { FormFieldModel } from '../core/form-field.model'; + +@Component({ + moduleId: module.id, + selector: 'attach-widget', + templateUrl: './attach.widget.html', + styleUrls: ['./attach.widget.css'] +}) +export class AttachWidget extends WidgetComponent implements OnInit { + + selectedFolderPathId: string; + selectedFolderSiteId: string; + selectedFolderSiteName: string; + selectedFolderAccountId: string; + fileName: string; + hasFile: boolean; + selectedFolderNodes: [ExternalContent]; + selectedFile: ExternalContent; + + @Input() + field: FormFieldModel; + + @Output() + fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>(); + + @ViewChild('dialog') + dialog: any; + + constructor(private contentService: ActivitiAlfrescoContentService) { + super(); + } + + ngOnInit() { + if (this.field) { + if (this.field.value) { + this.hasFile = true; + } + + let params = this.field.params; + + if (params && + params.fileSource && + params.fileSource.selectedFolder) { + this.selectedFolderSiteId = params.fileSource.selectedFolder.siteId; + this.selectedFolderSiteName = params.fileSource.selectedFolder.site; + this.setupFileBrowser(); + this.getExternalContentNodes(); + } + } + } + + setupFileBrowser() { + if (this.field) { + let params = this.field.params; + this.selectedFolderPathId = params.fileSource.selectedFolder.pathId; + this.selectedFolderAccountId = params.fileSource.selectedFolder.accountId; + } + } + + getLinkedFileName(): string { + let result = this.fileName; + + if (this.selectedFile && + this.selectedFile.title) { + result = this.selectedFile.title; + } + if (this.field && + this.field.value && + this.field.value.length > 0 && + this.field.value[0].name) { + result = this.field.value[0].name; + } + + return result; + } + + getExternalContentNodes() { + this.contentService.getAlfrescoNodes(this.selectedFolderAccountId, this.selectedFolderPathId) + .subscribe( + nodes => this.selectedFolderNodes = nodes, + error => this.handleError(error) + ); + } + + selectFile(node: ExternalContent, $event: any) { + this.contentService.linkAlfrescoNode(this.selectedFolderAccountId, node, this.selectedFolderSiteId).subscribe( + (link: ExternalContentLink) => { + this.selectedFile = node; + this.field.value = [link]; + this.field.json.value = [link]; + this.closeDialog(); + this.fieldChanged.emit(this.field); + } + ); + } + + selectFolder(node: ExternalContent, $event: any) { + this.selectedFolderPathId = node.id; + this.getExternalContentNodes(); + } + + showDialog(): boolean { + this.setupFileBrowser(); + this.getExternalContentNodes(); + + if (this.dialog) { + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); + } + + this.dialog.nativeElement.showModal(); + return true; + } + return false; + } + + private closeDialog() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } + + cancel() { + this.closeDialog(); + } + + reset() { + this.field.value = null; + this.field.json.value = null; + this.hasFile = false; + } + +} 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 index 67862db94d..a5ca9176e3 100644 --- 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 @@ -1,7 +1,9 @@ <label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" [attr.for]="field.id"> <input type="checkbox" [attr.id]="field.id" + [attr.required]="isRequired()" class="mdl-checkbox__input" + [checked]="field.value" [(ngModel)]="field.value" (ngModelChange)="checkVisibility(field)" [disabled]="field.readOnly"> 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 index 168a6972b3..b49f0718a6 100644 --- 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 @@ -18,11 +18,8 @@ import { Component } from '@angular/core'; import { WidgetComponent } from './../widget.component'; -declare let __moduleName: string; -declare var componentHandler; - @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'checkbox-widget', templateUrl: './checkbox.widget.html' }) 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 index 6cffc9730c..ccab11975e 100644 --- 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 @@ -1,6 +1,6 @@ <div class="container-widget"> - <div *ngIf="content?.isGroup()" class="container-widget__header"> - <h4 class="container-widget__header-text" + <div *ngIf="content?.isGroup() && content?.isVisible" class="container-widget__header"> + <h4 class="container-widget__header-text" id="container-header" [class.collapsible]="content?.isCollapsible()"> <button *ngIf="content?.isCollapsible()" alfresco-mdl-button @@ -8,10 +8,10 @@ (click)="onExpanderClicked()"> <i class="material-icons">{{ content?.isExpanded ? 'expand_less' : 'expand_more' }}</i> </button> - <span (click)="onExpanderClicked()">{{content.name}}</span> + <span (click)="onExpanderClicked()" id="container-header-label">{{content.name}}</span> </h4> </div> - <div class="mdl-grid" *ngIf="content?.isExpanded"> + <div class="mdl-grid" *ngIf="content?.isVisible && content?.isExpanded"> <div *ngFor="let col of content.columns" class="mdl-cell mdl-cell--{{col.size}}-col"> <div class="mdl-grid" *ngIf="col.hasFields()"> <div *ngFor="let field of col.fields" class="mdl-cell mdl-cell--12-col"> @@ -44,7 +44,23 @@ <display-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-text-widget> </div> <div *ngSwitchCase="'upload'"> - <upload-widget [field]="field" (fieldChanged)="fieldChanged($event);"></upload-widget> + <upload-widget *ngIf="!field.params.link" [field]="field" (fieldChanged)="fieldChanged($event);"></upload-widget> + <attach-widget *ngIf="field.params.link" [field]="field" (fieldChanged)="fieldChanged($event);"></attach-widget> + </div> + <div *ngSwitchCase="'typeahead'"> + <typeahead-widget [field]="field" (fieldChanged)="fieldChanged($event);"></typeahead-widget> + </div> + <div *ngSwitchCase="'functional-group'"> + <functional-group-widget [field]="field" (fieldChanged)="fieldChanged($event);"></functional-group-widget> + </div> + <div *ngSwitchCase="'people'"> + <people-widget [field]="field" (fieldChanged)="fieldChanged($event);"></people-widget> + </div> + <div *ngSwitchCase="'date'"> + <date-widget [field]="field" (fieldChanged)="fieldChanged($event);"></date-widget> + </div> + <div *ngSwitchCase="'amount'"> + <amount-widget [field]="field" (fieldChanged)="fieldChanged($event);"></amount-widget> </div> <div *ngSwitchDefault> <span>UNKNOWN WIDGET TYPE: {{field.type}}</span> 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 index eb721dfc9f..e3da7ed9c2 100644 --- 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 @@ -15,19 +15,22 @@ * 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'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { CoreModule } from 'ng2-alfresco-core'; +import { WIDGET_DIRECTIVES } from '../index'; +import { fakeFormJson } from '../../../services/assets/widget-visibility.service.mock'; describe('ContainerWidget', () => { let componentHandler; beforeEach(() => { - componentHandler = jasmine.createSpyObj('componentHandler', [ + componentHandler = jasmine.createSpyObj('componentHandler', [ 'upgradeAllRegistered' ]); @@ -50,7 +53,7 @@ describe('ContainerWidget', () => { it('should toggle underlying group container', () => { let container = new ContainerModel(new FormModel(), { - type: FormFieldTypes.GROUP, + type: FormFieldTypes.GROUP, params: { allowCollapse: true } @@ -68,7 +71,7 @@ describe('ContainerWidget', () => { it('should toggle only collapsible container', () => { let container = new ContainerModel(new FormModel(), { - type: FormFieldTypes.GROUP + type: FormFieldTypes.GROUP }); let widget = new ContainerWidget(); @@ -81,7 +84,7 @@ describe('ContainerWidget', () => { it('should toggle only group container', () => { let container = new ContainerModel(new FormModel(), { - type: FormFieldTypes.CONTAINER, + type: FormFieldTypes.CONTAINER, params: { allowCollapse: true } @@ -109,4 +112,101 @@ describe('ContainerWidget', () => { widget.fieldChanged(fakeField); }); + describe('when template is ready', () => { + let containerWidgetComponent: ContainerWidget; + let fixture: ComponentFixture<ContainerWidget>; + let element: HTMLElement; + let fakeContainerVisible: ContainerModel; + let fakeContainerInvisible: ContainerModel; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [WIDGET_DIRECTIVES] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(ContainerWidget); + containerWidgetComponent = fixture.componentInstance; + element = fixture.nativeElement; + }); + })); + + beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']); + window['componentHandler'] = componentHandler; + fakeContainerVisible = new ContainerModel(new FormModel(fakeFormJson), { + fieldType: FormFieldTypes.GROUP, + id: 'fake-cont-id-1', + name: 'fake-cont-1-name', + type: FormFieldTypes.GROUP + }); + fakeContainerInvisible = new ContainerModel(new FormModel(fakeFormJson), { + fieldType: FormFieldTypes.GROUP, + id: 'fake-cont-id-2', + name: 'fake-cont-2-name', + type: FormFieldTypes.GROUP + }); + fakeContainerVisible.field.isVisible = true; + fakeContainerInvisible.field.isVisible = false; + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + it('should show the container header when it is visible', () => { + containerWidgetComponent.content = fakeContainerVisible; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#container-header')).toBeDefined(); + expect(element.querySelector('#container-header')).not.toBeNull(); + expect(element.querySelector('#container-header-label')).toBeDefined(); + expect(element.querySelector('#container-header-label').innerHTML).toContain('fake-cont-1-name'); + }); + }); + + it('should not show the container header when it is not visible', () => { + containerWidgetComponent.content = fakeContainerInvisible; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#container-header')).toBeNull(); + expect(element.querySelector('#container-header-label')).toBeNull(); + }); + }); + + it('should hide header when it becomes not visible', async(() => { + containerWidgetComponent.content = fakeContainerVisible; + fixture.detectChanges(); + containerWidgetComponent.formValueChanged.subscribe((res) => { + containerWidgetComponent.content.field.isVisible = false; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#container-header')).toBeNull(); + expect(element.querySelector('#container-header-label')).toBeNull(); + }); + }); + containerWidgetComponent.fieldChanged(null); + })); + + it('should show header when it becomes visible', async(() => { + containerWidgetComponent.content = fakeContainerInvisible; + containerWidgetComponent.formValueChanged.subscribe((res) => { + containerWidgetComponent.content.field.isVisible = true; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#container-header')).toBeDefined(); + expect(element.querySelector('#container-header')).not.toBeNull(); + expect(element.querySelector('#container-header-label')).toBeDefined(); + expect(element.querySelector('#container-header-label').innerHTML).toContain('fake-cont-2-name'); + }); + }); + containerWidgetComponent.fieldChanged(null); + })); + + }); + }); 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 index aa8e0ddab9..713de93949 100644 --- 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 @@ -16,24 +16,13 @@ */ 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; +import { ContainerModel, FormFieldModel } from './../core/index'; @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'container-widget', templateUrl: './container.widget.html', - styleUrls: ['./container.widget.css'], - directives: [ - MATERIAL_DESIGN_DIRECTIVES, - PRIMITIVE_WIDGET_DIRECTIVES - ] + styleUrls: ['./container.widget.css'] }) export class ContainerWidget implements AfterViewInit { 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 index 5296d54288..2a983fae37 100644 --- 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 @@ -15,8 +15,8 @@ * limitations under the License. */ -import { it, describe, expect } from '@angular/core/testing'; import { ContainerColumnModel } from './container-column.model'; +import { FormModel } from './form.model'; import { FormFieldModel } from './form-field.model'; describe('ContainerColumnModel', () => { @@ -35,7 +35,7 @@ describe('ContainerColumnModel', () => { column.fields = []; expect(column.hasFields()).toBeFalsy(); - column.fields = [new FormFieldModel(null, null)]; + column.fields = [new FormFieldModel(new FormModel(), null)]; expect(column.hasFields()).toBeTruthy(); }); 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 index 3f007d4869..cdea916fda 100644 --- 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 @@ -15,7 +15,6 @@ * 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'; @@ -70,6 +69,7 @@ describe('ContainerModel', () => { tab: '<tab>', numberOfColumns: 3, params: {}, + visibilityCondition: {}, fields: { '1': [ { id: 'field-1' }, @@ -106,7 +106,12 @@ describe('ContainerModel', () => { }); expect(container.isCollapsible()).toBeFalsy(); - container.type = FormFieldTypes.GROUP; + container = new ContainerModel(new FormModel(), { + type: FormFieldTypes.GROUP, + params: { + allowCollapse: true + } + }); expect(container.isCollapsible()).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 index dfeaf231b9..4de70c6642 100644 --- 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 @@ -22,20 +22,19 @@ 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; + field: FormFieldModel; numberOfColumns: number = 1; params: FormFieldMetadata = {}; columns: ContainerColumnModel[] = []; isExpanded: boolean = true; + get isVisible(): boolean { + return this.field.isVisible; + } + isGroup(): boolean { return this.type === FormFieldTypes.GROUP; } @@ -64,11 +63,7 @@ export class ContainerModel extends FormWidgetModel { 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.field = new FormFieldModel(form, json); this.numberOfColumns = <number> json.numberOfColumns; this.params = <FormFieldMetadata> json.params || {}; @@ -94,4 +89,22 @@ export class ContainerModel extends FormWidgetModel { this.isExpanded = !this.isCollapsedByDefault(); } } + + getFormFields(): FormFieldModel[] { + let result: FormFieldModel[] = []; + + if (this.field) { + result.push(this.field); + } + + for (let j = 0; j < this.columns.length; j++) { + let column = this.columns[j]; + for (let k = 0; k < column.fields.length; k++) { + let field = column.fields[k]; + result.push(field); + } + } + + return result; + } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-column.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-column.ts new file mode 100644 index 0000000000..a5b4692f8f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-column.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. + */ + +// maps to: com.activiti.model.editor.form.ColumnDefinitionRepresentation +export interface DynamicTableColumn { + + id: string; + name: string; + type: string; + value: any; + optionType: string; + options: DynamicTableColumnOption[]; + restResponsePath: string; + restUrl: string; + restIdProperty: string; + restLabelProperty: string; + amountCurrency: string; + amountEnableFractions: boolean; + required: boolean; + editable: boolean; + sortable: boolean; + visible: boolean; + + // TODO: com.activiti.domain.idm.EndpointConfiguration.EndpointConfigurationRepresentation + endpoint: any; + // TODO: com.activiti.model.editor.form.RequestHeaderRepresentation + requestHeaders: any; +} + +// maps to: com.activiti.model.editor.form.OptionRepresentation +export interface DynamicTableColumnOption { + id: string; + name: string; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-row.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-row.ts new file mode 100644 index 0000000000..ce471b13b2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table-row.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. + */ + +export interface DynamicTableRow { + + isNew: boolean; + selected: boolean; + value: any; + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table.model.ts new file mode 100644 index 0000000000..257beb93ae --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/dynamic-table.model.ts @@ -0,0 +1,287 @@ +/*! + * @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'; +import { FormFieldModel } from './form-field.model'; +import { DynamicTableColumn } from './dynamic-table-column'; +import { DynamicTableRow } from './dynamic-table-row'; + +export class DynamicTableModel extends FormWidgetModel { + + field: FormFieldModel; + columns: DynamicTableColumn[] = []; + visibleColumns: DynamicTableColumn[] = []; + rows: DynamicTableRow[] = []; + + private _selectedRow: DynamicTableRow; + private _validators: CellValidator[] = []; + + get selectedRow(): DynamicTableRow { + return this._selectedRow; + } + + set selectedRow(value: DynamicTableRow) { + if (this._selectedRow && this._selectedRow === value) { + this._selectedRow.selected = false; + this._selectedRow = null; + return; + } + + this.rows.forEach(row => row.selected = false); + + this._selectedRow = value; + + if (value) { + this._selectedRow.selected = true; + } + } + + constructor(form: FormModel, json?: any) { + super(form, json); + + if (json) { + this.field = new FormFieldModel(form, json); + + if (json.columnDefinitions) { + this.columns = json.columnDefinitions.map(obj => <DynamicTableColumn> obj); + this.visibleColumns = this.columns.filter(col => col.visible); + } + + if (json.value) { + this.rows = json.value.map(obj => <DynamicTableRow> { selected: false, value: obj }); + } + } + + this._validators = [ + new RequiredCellValidator(), + new DateCellValidator(), + new NumberCellValidator() + ]; + } + + flushValue() { + if (this.field) { + this.field.value = this.rows.map(r => r.value); + this.field.updateForm(); + } + } + + moveRow(row: DynamicTableRow, offset: number) { + let oldIndex = this.rows.indexOf(row); + if (oldIndex > -1) { + let newIndex = (oldIndex + offset); + + if (newIndex < 0) { + newIndex = 0; + } else if (newIndex >= this.rows.length) { + newIndex = this.rows.length; + } + + let arr = this.rows.slice(); + arr.splice(oldIndex, 1); + arr.splice(newIndex, 0, row); + this.rows = arr; + + this.flushValue(); + } + } + + deleteRow(row: DynamicTableRow) { + if (row) { + if (this.selectedRow === row) { + this.selectedRow = null; + } + let idx = this.rows.indexOf(row); + if (idx > -1) { + this.rows.splice(idx, 1); + this.flushValue(); + } + } + } + + addRow(row: DynamicTableRow) { + if (row) { + this.rows.push(row); + // this.selectedRow = row; + } + } + + validateRow(row: DynamicTableRow): DynamicRowValidationSummary { + let summary = <DynamicRowValidationSummary> { + isValid: true, + text: null + }; + + if (row) { + for (let col of this.columns) { + for (let validator of this._validators) { + if (!validator.validate(row, col, summary)) { + return summary; + } + } + } + } + + return summary; + } + + getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any { + let result = row.value[column.id]; + + if (column.type === 'Dropdown') { + if (result) { + return result.name; + } + } + + if (column.type === 'Boolean') { + return result ? true : false; + } + + if (column.type === 'Date') { + if (result) { + return moment(result.split('T')[0], 'YYYY-MM-DD').format('DD-MM-YYYY'); + } + } + + return result || ''; + } + + getDisplayText(column: DynamicTableColumn): string { + let result = column.name; + if (column.type === 'Amount') { + let currency = column.amountCurrency || '$'; + result = `${column.name} (${currency})`; + } + return result; + } +} + +export interface DynamicRowValidationSummary { + + isValid: boolean; + text: string; + +} + +export interface CellValidator { + + isSupported(column: DynamicTableColumn): boolean; + validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean; + +} + +export class RequiredCellValidator implements CellValidator { + + private supportedTypes: string[] = [ + 'String', + 'Number', + 'Amount', + 'Date', + 'Dropdown' + ]; + + isSupported(column: DynamicTableColumn): boolean { + return column && column.required && this.supportedTypes.indexOf(column.type) > -1; + } + + validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean { + if (this.isSupported(column)) { + let value = row.value[column.id]; + if (column.required) { + if (value === null || value === undefined || value === '') { + if (summary) { + summary.isValid = false; + summary.text = `Field '${column.name}' is required.`; + } + return false; + } + } + } + + return true; + } +} + +export class DateCellValidator implements CellValidator { + + private supportedTypes: string[] = [ + 'Date' + ]; + + isSupported(column: DynamicTableColumn): boolean { + return column && column.editable && this.supportedTypes.indexOf(column.type) > -1; + } + + validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean { + + if (this.isSupported(column)) { + let value = row.value[column.id]; + let dateValue = moment(value, 'D-M-YYYY'); + if (!dateValue.isValid()) { + if (summary) { + summary.isValid = false; + summary.text = `Invalid '${column.name}' format.`; + } + return false; + } + } + + return true; + } +} + +export class NumberCellValidator implements CellValidator { + + private supportedTypes: string[] = [ + 'Number', + 'Amount' + ]; + + isSupported(column: DynamicTableColumn): boolean { + return column && column.required && this.supportedTypes.indexOf(column.type) > -1; + } + + isNumber(value: any): boolean { + if (value === null || value === undefined || value === '') { + return false; + } + + return !isNaN(+value); + } + + validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean { + + if (this.isSupported(column)) { + let value = row.value[column.id]; + if (value === null || + value === undefined || + value === '' || + this.isNumber(value)) { + return true; + } + + if (summary) { + summary.isValid = false; + summary.text = `Field '${column.name}' must be a number.`; + } + return false; + } + return true; + } +} diff --git a/ng2-components/ng2-activiti-processlist/src/models/comment.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/external-content-link.ts similarity index 62% rename from ng2-components/ng2-activiti-processlist/src/models/comment.model.ts rename to ng2-components/ng2-activiti-form/src/components/widgets/core/external-content-link.ts index d275e7ca1e..2fc6f8ad09 100644 --- a/ng2-components/ng2-activiti-processlist/src/models/comment.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/external-content-link.ts @@ -15,24 +15,18 @@ * limitations under the License. */ -/** - * - * Comment submitted against a process - * - * @returns {Comment} . - */ -import { User } from './user.model'; - -export class Comment { - id: number; - message: string; +export interface ExternalContentLink { + contentAvailable: boolean; 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; - } + createdBy: any; + id: number; + link: boolean; + mimeType: string; + name: string; + previewStatus: string; + relatedContent: boolean; + simpleType: string; + source: string; + sourceId: string; + thumbnailStatus: string; } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/external-content.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/external-content.ts new file mode 100644 index 0000000000..88bc0dece1 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/external-content.ts @@ -0,0 +1,23 @@ +/*! + * @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 ExternalContent { + folder: boolean; + id: string; + simpleType: string; + title: string; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-file-source.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-file-source.ts new file mode 100644 index 0000000000..6b86dcb5d1 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-file-source.ts @@ -0,0 +1,25 @@ +/*! + * @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 {FormFieldSelectedFolder} from './form-field-selected-folder'; + +export interface FormFieldFileSource { + metadataAllowed: boolean; + name: string; + selectedFolder: FormFieldSelectedFolder; + serviceId: string; +} 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 index 02378e3b41..102695019c 100644 --- 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 @@ -15,6 +15,10 @@ * limitations under the License. */ +import {FormFieldFileSource} from './form-field-file-source'; + export interface FormFieldMetadata { [key: string]: any; + fileSource?: FormFieldFileSource; + link?: boolean; } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-selected-folder.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-selected-folder.ts new file mode 100644 index 0000000000..abd472f109 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-selected-folder.ts @@ -0,0 +1,25 @@ +/*! + * @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 FormFieldSelectedFolder { + accountId: string; + folderTree: [any]; + path: string; + pathId: string; + site: string; + siteId: 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 index 627bcfe5d2..9d0e23f5be 100644 --- 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 @@ -18,12 +18,22 @@ export class FormFieldTypes { static CONTAINER: string = 'container'; static GROUP: string = 'group'; + static DYNAMIC_TABLE: string = 'dynamic-table'; + static TEXT: string = 'text'; + static MULTILINE_TEXT: string = 'multi-line-text'; 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 TYPEAHEAD: string = 'typeahead'; + static FUNCTIONAL_GROUP: string = 'functional-group'; + static PEOPLE: string = 'people'; + static BOOLEAN: string = 'boolean'; + static NUMBER: string = 'integer'; + static DATE: string = 'date'; + static AMOUNT: string = 'amount'; static READONLY_TYPES: string[] = [ FormFieldTypes.HYPERLINK, diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.spec.ts new file mode 100644 index 0000000000..3049242d9b --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.spec.ts @@ -0,0 +1,491 @@ +/*! + * @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'; +import { FormFieldModel } from './form-field.model'; +import { FormFieldOption } from './form-field-option'; +import { FormFieldTypes } from './form-field-types'; +import { + RequiredFieldValidator, + NumberFieldValidator, + MinLengthFieldValidator, + MaxLengthFieldValidator, + MinValueFieldValidator, + MaxValueFieldValidator, + RegExFieldValidator +} from './form-field-validator'; + +describe('FormFieldValidator', () => { + + describe('RequiredFieldValidator', () => { + + let validator: RequiredFieldValidator; + + beforeEach(() => { + validator = new RequiredFieldValidator(); + }); + + it('should require [required] setting', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + value: '<value>' + }); + + field.required = false; + expect(validator.isSupported(field)).toBeFalsy(); + expect(validator.validate(field)).toBeTruthy(); + + field.required = true; + expect(validator.isSupported(field)).toBeTruthy(); + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should skip unsupported type', () => { + let field = new FormFieldModel(new FormModel(), { type: 'wrong-type' }); + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail for dropdown with empty value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.DROPDOWN, + value: '<empty>', + hasEmptyValue: true, + required: true + }); + + field.emptyOption = <FormFieldOption> { id: '<empty>' }; + expect(validator.validate(field)).toBeFalsy(); + + field.value = '<non-empty>'; + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail for radio buttons', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.RADIO_BUTTONS, + required: true, + value: 'one', + options: [{ id: 'two', name: 'two' }] + }); + + expect(validator.validate(field)).toBeFalsy(); + }); + + it('should succeed for radio buttons', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.RADIO_BUTTONS, + required: true, + value: 'two', + options: [{ id: 'two', name: 'two' }] + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail for upload', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.UPLOAD, + value: null, + required: true + }); + + field.value = null; + expect(validator.validate(field)).toBeFalsy(); + + field.value = []; + expect(validator.validate(field)).toBeFalsy(); + }); + + it('should succeed for upload', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.UPLOAD, + value: [{}], + required: true + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail for text', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + value: null, + required: true + }); + + field.value = null; + expect(validator.validate(field)).toBeFalsy(); + + field.value = ''; + expect(validator.validate(field)).toBeFalsy(); + }); + + it('should succeed for text', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + value: '<value>', + required: true + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + }); + + describe('NumberFieldValidator', () => { + + let validator: NumberFieldValidator; + + beforeEach(() => { + validator = new NumberFieldValidator(); + }); + + it('should verify number', () => { + expect(NumberFieldValidator.isNumber('1')).toBeTruthy(); + expect(NumberFieldValidator.isNumber('1.0')).toBeTruthy(); + expect(NumberFieldValidator.isNumber('-1')).toBeTruthy(); + }); + + it('should not verify number', () => { + expect(NumberFieldValidator.isNumber(null)).toBeFalsy(); + expect(NumberFieldValidator.isNumber(undefined)).toBeFalsy(); + expect(NumberFieldValidator.isNumber('')).toBeFalsy(); + expect(NumberFieldValidator.isNumber('one')).toBeFalsy(); + expect(NumberFieldValidator.isNumber('1q')).toBeFalsy(); + }); + + it('should allow empty number value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: null + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail for wrong number value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: '<value>' + }); + + field.validationSummary = null; + expect(validator.validate(field)).toBeFalsy(); + expect(field.validationSummary).not.toBeNull(); + }); + + }); + + describe('MinLengthFieldValidator', () => { + + let validator: MinLengthFieldValidator; + + beforeEach(() => { + validator = new MinLengthFieldValidator(); + }); + + it('should require minLength defined', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT + }); + + expect(validator.isSupported(field)).toBeFalsy(); + + field.minLength = 10; + expect(validator.isSupported(field)).toBeTruthy(); + }); + + it('should allow empty values', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + minLength: 10, + value: null + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should succeed text validation', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + minLength: 3, + value: '1234' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail text validation', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + minLength: 3, + value: '12' + }); + + field.validationSummary = null; + expect(validator.validate(field)).toBeFalsy(); + expect(field.validationSummary).not.toBeNull(); + }); + + }); + + describe('MaxLengthFieldValidator', () => { + + let validator: MaxLengthFieldValidator; + + beforeEach(() => { + validator = new MaxLengthFieldValidator(); + }); + + it('should require maxLength defined', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT + }); + + expect(validator.isSupported(field)).toBeFalsy(); + + field.maxLength = 10; + expect(validator.isSupported(field)).toBeTruthy(); + }); + + it('should allow empty values', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + maxLength: 10, + value: null + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should succeed text validation', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + maxLength: 3, + value: '123' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail text validation', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + maxLength: 3, + value: '1234' + }); + + field.validationSummary = null; + expect(validator.validate(field)).toBeFalsy(); + expect(field.validationSummary).not.toBeNull(); + }); + }); + + describe('MinValueFieldValidator', () => { + + let validator: MinValueFieldValidator; + + beforeEach(() => { + validator = new MinValueFieldValidator(); + }); + + it('should require minValue defined', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER + }); + expect(validator.isSupported(field)).toBeFalsy(); + + field.minValue = '1'; + expect(validator.isSupported(field)).toBeTruthy(); + }); + + it('should support numeric widgets only', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + minValue: '1' + }); + + expect(validator.isSupported(field)).toBeTruthy(); + + field.type = FormFieldTypes.TEXT; + expect(validator.isSupported(field)).toBeFalsy(); + }); + + it('should allow empty values', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: null, + minValue: '1' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should succeed for unsupported types', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should succeed validating value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: '10', + minValue: '10' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail validating value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: '9', + minValue: '10' + }); + + field.validationSummary = null; + expect(validator.validate(field)).toBeFalsy(); + expect(field.validationSummary).not.toBeNull(); + }); + + }); + + describe('MaxValueFieldValidator', () => { + + let validator: MaxValueFieldValidator; + + beforeEach(() => { + validator = new MaxValueFieldValidator(); + }); + + it('should require maxValue defined', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER + }); + expect(validator.isSupported(field)).toBeFalsy(); + + field.maxValue = '1'; + expect(validator.isSupported(field)).toBeTruthy(); + }); + + it('should support numeric widgets only', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + maxValue: '1' + }); + + expect(validator.isSupported(field)).toBeTruthy(); + + field.type = FormFieldTypes.TEXT; + expect(validator.isSupported(field)).toBeFalsy(); + }); + + it('should allow empty values', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: null, + maxValue: '1' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should succeed for unsupported types', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should succeed validating value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: '10', + maxValue: '10' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail validating value', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.NUMBER, + value: '11', + maxValue: '10' + }); + + field.validationSummary = null; + expect(validator.validate(field)).toBeFalsy(); + expect(field.validationSummary).not.toBeNull(); + }); + + }); + + describe('RegExFieldValidator', () => { + + let validator: RegExFieldValidator; + + beforeEach(() => { + validator = new RegExFieldValidator(); + }); + + it('should require regex pattern to be defined', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT + }); + expect(validator.isSupported(field)).toBeFalsy(); + + field.regexPattern = '<pattern>'; + expect(validator.isSupported(field)).toBeTruthy(); + }); + + it('should allow empty values', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + value: null, + regexPattern: 'pattern' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should succeed validating regex', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + value: 'pattern', + regexPattern: 'pattern' + }); + + expect(validator.validate(field)).toBeTruthy(); + }); + + it('should fail validating regex', () => { + let field = new FormFieldModel(new FormModel(), { + type: FormFieldTypes.TEXT, + value: 'some value', + regexPattern: 'pattern' + }); + + expect(validator.validate(field)).toBeFalsy(); + }); + + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.ts new file mode 100644 index 0000000000..c7ad7c23d5 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.ts @@ -0,0 +1,349 @@ +/*! + * @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 { FormFieldModel } from './form-field.model'; +import { FormFieldTypes } from './form-field-types'; + +export interface FormFieldValidator { + + isSupported(field: FormFieldModel): boolean; + validate(field: FormFieldModel): boolean; + +} + +export class RequiredFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.TEXT, + FormFieldTypes.MULTILINE_TEXT, + FormFieldTypes.NUMBER, + FormFieldTypes.TYPEAHEAD, + FormFieldTypes.DROPDOWN, + FormFieldTypes.PEOPLE, + FormFieldTypes.FUNCTIONAL_GROUP, + FormFieldTypes.RADIO_BUTTONS, + FormFieldTypes.UPLOAD, + FormFieldTypes.AMOUNT, + FormFieldTypes.DYNAMIC_TABLE + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + field.required; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field)) { + + if (field.type === FormFieldTypes.DROPDOWN) { + if (field.hasEmptyValue && field.emptyOption) { + if (field.value === field.emptyOption.id) { + return false; + } + } + } + + if (field.type === FormFieldTypes.RADIO_BUTTONS) { + let option = field.options.find(opt => opt.id === field.value); + return !!option; + } + + if (field.type === FormFieldTypes.UPLOAD) { + return field.value && field.value.length > 0; + } + + if (field.type === FormFieldTypes.DYNAMIC_TABLE) { + return field.value && field.value instanceof Array && field.value.length > 0; + } + + if (field.value === null || field.value === undefined || field.value === '') { + return false; + } + } + return true; + } + +} + +export class NumberFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.NUMBER, + FormFieldTypes.AMOUNT + ]; + + static isNumber(value: any): boolean { + if (value === null || value === undefined || value === '') { + return false; + } + + return !isNaN(+value); + } + + isSupported(field: FormFieldModel): boolean { + return field && this.supportedTypes.indexOf(field.type) > -1; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field)) { + if (field.value === null || + field.value === undefined || + field.value === '' || + NumberFieldValidator.isNumber(field.value)) { + return true; + } + field.validationSummary = 'Input must be a number'; + return false; + } + return true; + } +} + +export class DateFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.DATE + ]; + + // Validates that the input string is a valid date formatted as <dateFormat> (default D-M-YYYY) + static isValidDate(dateString: string, dateFormat: string = 'D-M-YYYY'): boolean { + if (dateString) { + let d = moment(dateString.split('T')[0], dateFormat, true); + return d.isValid(); + } + + return false; + } + + isSupported(field: FormFieldModel): boolean { + return field && this.supportedTypes.indexOf(field.type) > -1; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + if (DateFieldValidator.isValidDate(field.value)) { + return true; + } + field.validationSummary = 'Invalid date format'; + return false; + } + return true; + } +} + +export class MinDateFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.DATE + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + !!field.minValue; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + const dateFormat = 'D-M-YYYY'; + + if (!DateFieldValidator.isValidDate(field.value, dateFormat)) { + field.validationSummary = 'Invalid date format'; + return false; + } + + // remove time and timezone info + let d = moment(field.value.split('T')[0], dateFormat); + let min = moment(field.minValue, dateFormat); + + if (d.isBefore(min)) { + field.validationSummary = `Should not be less than ${field.minValue}`; + return false; + } + } + return true; + } +} + +export class MaxDateFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.DATE + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + !!field.maxValue; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + const dateFormat = 'D-M-YYYY'; + + if (!DateFieldValidator.isValidDate(field.value, dateFormat)) { + field.validationSummary = 'Invalid date format'; + return false; + } + + // remove time and timezone info + let d = moment(field.value.split('T')[0], dateFormat); + let max = moment(field.maxValue, dateFormat); + + if (d.isAfter(max)) { + field.validationSummary = `Should not be greater than ${field.maxValue}`; + return false; + } + } + return true; + } +} + +export class MinLengthFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.TEXT, + FormFieldTypes.MULTILINE_TEXT + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + field.minLength > 0; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + if (field.value.length >= field.minLength) { + return true; + } + field.validationSummary = `Should be at least ${field.minLength} characters long.`; + return false; + } + return true; + } +} + +export class MaxLengthFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.TEXT, + FormFieldTypes.MULTILINE_TEXT + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + field.maxLength > 0; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + if (field.value.length <= field.maxLength) { + return true; + } + field.validationSummary = `Should be ${field.maxLength} characters maximum.`; + return false; + } + return true; + } +} + +export class MinValueFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.NUMBER, + FormFieldTypes.AMOUNT + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + NumberFieldValidator.isNumber(field.minValue); + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + let value: number = +field.value; + let minValue: number = +field.minValue; + + if (value >= minValue) { + return true; + } + field.validationSummary = `Should not be less than ${field.minValue}`; + return false; + } + + return true; + } +} + +export class MaxValueFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.NUMBER, + FormFieldTypes.AMOUNT + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + NumberFieldValidator.isNumber(field.maxValue); + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + let value: number = +field.value; + let maxValue: number = +field.maxValue; + + if (value <= maxValue) { + return true; + } + field.validationSummary = `Should not be greater than ${field.maxValue}`; + return false; + } + + return true; + } +} + +export class RegExFieldValidator implements FormFieldValidator { + + private supportedTypes = [ + FormFieldTypes.TEXT, + FormFieldTypes.MULTILINE_TEXT + ]; + + isSupported(field: FormFieldModel): boolean { + return field && + this.supportedTypes.indexOf(field.type) > -1 && + !!field.regexPattern; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field) && field.value) { + if (field.value.length > 0 && field.value.match(new RegExp('^' + field.regexPattern + '$'))) { + return true; + } + field.validationSummary = 'Invalid value format'; + return false; + } + return true; + } + +} 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 index aa987b5e2a..90d2a9eece 100644 --- 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 @@ -15,12 +15,10 @@ * 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', () => { @@ -113,15 +111,6 @@ describe('FormFieldModel', () => { 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, @@ -145,19 +134,6 @@ describe('FormFieldModel', () => { 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, 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 index 22bd3822ae..11bd17fe82 100644 --- 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 @@ -21,12 +21,29 @@ import { FormFieldTypes } from './form-field-types'; import { FormFieldMetadata } from './form-field-metadata'; import { FormModel } from './form.model'; import { WidgetVisibilityModel } from '../../../models/widget-visibility.model'; +import { + FormFieldValidator, + RequiredFieldValidator, + NumberFieldValidator, + MinLengthFieldValidator, + MaxLengthFieldValidator, + MinValueFieldValidator, + MaxValueFieldValidator, + RegExFieldValidator, + DateFieldValidator, + MinDateFieldValidator, + MaxDateFieldValidator +} from './form-field-validator'; + +declare var moment: any; export class FormFieldModel extends FormWidgetModel { private _value: string; private _readOnly: boolean = false; + private _isValid: boolean = true; + // model members fieldType: string; id: string; name: string; @@ -35,6 +52,11 @@ export class FormFieldModel extends FormWidgetModel { overrideId: boolean; tab: string; colspan: number = 1; + minLength: number = 0; + maxLength: number = 0; + minValue: string; + maxValue: string; + regexPattern: string; options: FormFieldOption[] = []; restUrl: string; restResponsePath: string; @@ -48,6 +70,13 @@ export class FormFieldModel extends FormWidgetModel { displayText: string; isVisible: boolean = true; visibilityCondition: WidgetVisibilityModel = null; + enableFractions: boolean = false; + currency: string = null; + + // advanced members + emptyOption: FormFieldOption; + validationSummary: string; + validators: FormFieldValidator[] = []; get value(): any { return this._value; @@ -55,6 +84,7 @@ export class FormFieldModel extends FormWidgetModel { set value(v: any) { this._value = v; + this.validate(); this.updateForm(); } @@ -65,6 +95,27 @@ export class FormFieldModel extends FormWidgetModel { return this._readOnly; } + get isValid(): boolean { + return this._isValid; + } + + validate(): boolean { + this.validationSummary = null; + + // TODO: consider doing that on value setter and caching result + if (this.validators && this.validators.length > 0) { + for (let i = 0; i < this.validators.length; i++) { + if (!this.validators[i].validate(this)) { + this._isValid = false; + return this._isValid; + } + } + } + + this._isValid = true; + return this._isValid; + } + constructor(form: FormModel, json?: any) { super(form, json); @@ -82,6 +133,11 @@ export class FormFieldModel extends FormWidgetModel { this.restIdProperty = json.restIdProperty; this.restLabelProperty = json.restLabelProperty; this.colspan = <number> json.colspan; + this.minLength = <number> json.minLength || 0; + this.maxLength = <number> json.maxLength || 0; + this.minValue = json.minValue; + this.maxValue = json.maxValue; + this.regexPattern = json.regexPattern; this.options = <FormFieldOption[]> json.options || []; this.hasEmptyValue = <boolean> json.hasEmptyValue; this.className = json.className; @@ -90,10 +146,29 @@ export class FormFieldModel extends FormWidgetModel { this.hyperlinkUrl = json.hyperlinkUrl; this.displayText = json.displayText; this.visibilityCondition = <WidgetVisibilityModel> json.visibilityCondition; - + this.enableFractions = <boolean>json.enableFractions; + this.currency = json.currency; this._value = this.parseValue(json); - this.updateForm(); } + + if (this.hasEmptyValue && this.options && this.options.length > 0) { + this.emptyOption = this.options[0]; + } + + this.validators = [ + new RequiredFieldValidator(), + new NumberFieldValidator(), + new MinLengthFieldValidator(), + new MaxLengthFieldValidator(), + new MinValueFieldValidator(), + new MaxValueFieldValidator(), + new RegExFieldValidator(), + new DateFieldValidator(), + new MinDateFieldValidator(), + new MaxDateFieldValidator() + ]; + + this.updateForm(); } parseValue(json: any): any { @@ -103,10 +178,15 @@ export class FormFieldModel extends FormWidgetModel { This is needed due to Activiti issue related to reading dropdown values as value string but saving back as object: { id: <id>, name: <name> } */ - // TODO: needs review if (json.type === FormFieldTypes.DROPDOWN) { - if (value === '') { - value = 'empty'; + if (json.hasEmptyValue && json.options) { + let options = <FormFieldOption[]> json.options || []; + if (options.length > 0) { + let emptyOption = json.options[0]; + if (value === '' || value === emptyOption.id || value === emptyOption.name) { + value = emptyOption.id; + } + } } } @@ -115,13 +195,25 @@ export class FormFieldModel extends FormWidgetModel { but saving back as object: { id: <id>, name: <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); + // Activiti has a bug with default radio button value where initial selection passed as `name` value + // so try resolving current one with a fallback to first entry via name or id + // TODO: needs to be reported and fixed at Activiti side + let entry: FormFieldOption[] = this.options.filter(opt => opt.id === value || opt.name === value); if (entry.length > 0) { value = entry[0].id; - } else if (this.options.length > 0) { - value = this.options[0].id; + } + } + + /* + This is needed due to Activiti displaying/editing dates in d-M-YYYY format + but storing on server in ISO8601 format (i.e. 2013-02-04T22:44:30.652Z) + */ + if (json.type === FormFieldTypes.DATE) { + if (value) { + let d = moment(value.split('T')[0], 'YYYY-M-D'); + if (d.isValid()) { + value = d.format('D-M-YYYY'); + } } } @@ -129,6 +221,9 @@ export class FormFieldModel extends FormWidgetModel { } updateForm() { + if (!this.form) { + return; + } switch (this.type) { case FormFieldTypes.DROPDOWN: @@ -150,9 +245,9 @@ export class FormFieldModel extends FormWidgetModel { This is needed due to Activiti issue related to reading radio button values as value string but saving back as object: { id: <id>, name: <name> } */ - let entry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value); - if (entry.length > 0) { - this.form.values[this.id] = entry[0]; + let rbEntry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value); + if (rbEntry.length > 0) { + this.form.values[this.id] = rbEntry[0]; } else if (this.options.length > 0) { this.form.values[this.id] = this.options[0]; } @@ -164,10 +259,34 @@ export class FormFieldModel extends FormWidgetModel { this.form.values[this.id] = null; } break; + case FormFieldTypes.TYPEAHEAD: + let taEntry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value); + if (taEntry.length > 0) { + this.form.values[this.id] = taEntry[0]; + } else if (this.options.length > 0) { + this.form.values[this.id] = null; + } + break; + case FormFieldTypes.DATE: + let d = moment(this.value, 'D-M-YYYY'); + if (d.isValid()) { + this.form.values[this.id] = `${d.format('YYYY-MM-DD')}T00:00:00.000Z`; + } else { + this.form.values[this.id] = null; + } + break; + case FormFieldTypes.NUMBER: + this.form.values[this.id] = parseInt(this.value, 10); + break; + case FormFieldTypes.AMOUNT: + this.form.values[this.id] = this.enableFractions ? parseFloat(this.value) : parseInt(this.value, 10); + break; default: if (!FormFieldTypes.isReadOnlyType(this.type)) { this.form.values[this.id] = this.value; } } + + this.form.onFormFieldChanged(this); } } 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 index 88dd417cc6..bc09703576 100644 --- 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 @@ -15,7 +15,6 @@ * limitations under the License. */ -import { it, describe, expect } from '@angular/core/testing'; import { FormModel } from './form.model'; import { FormOutcomeModel } from './form-outcome.model'; 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 index f0eb0abb05..219ddb7a41 100644 --- 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 @@ -23,25 +23,12 @@ 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-widget.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-widget.model.spec.ts index 0481b59432..18198a3cf6 100644 --- 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 @@ -15,21 +15,26 @@ * 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', () => { + class FormWidgetModelMock extends FormWidgetModel { + constructor(form: FormModel, json: any) { + super(form, json); + } + } + it('should store the form reference', () => { let form = new FormModel(); - let model = new FormWidgetModel(form, null); + let model = new FormWidgetModelMock(form, null); expect(model.form).toBe(form); }); it('should store original json', () => { let json = {}; - let model = new FormWidgetModel(null, json); + let model = new FormWidgetModelMock(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 index 39415c2547..7e5c173204 100644 --- 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 @@ -17,22 +17,28 @@ import { FormModel } from './form.model'; -export class FormWidgetModel { +export abstract class FormWidgetModel { - private _form: FormModel; - private _json: any; + readonly fieldType: string; + readonly id: string; + readonly name: string; + readonly type: string; + readonly tab: string; - get form(): FormModel { - return this._form; - } - - get json(): any { - return this._json; - } + readonly form: FormModel; + readonly json: any; constructor(form: FormModel, json: any) { - this._form = form; - this._json = json; + this.form = form; + this.json = json; + + if (json) { + this.fieldType = json.fieldType; + this.id = json.id; + this.name = json.name; + this.type = json.type; + this.tab = json.tab; + } } } 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 index 1f5d36304a..7fabcd0722 100644 --- 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 @@ -15,12 +15,12 @@ * 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'; +import { FormFieldTypes } from './form-field-types'; describe('FormModel', () => { @@ -119,8 +119,14 @@ describe('FormModel', () => { it('should parse fields', () => { let json = { fields: [ - { id: 'field1' }, - { id: 'field2' } + { + id: 'field1', + type: FormFieldTypes.CONTAINER + }, + { + id: 'field2', + type: FormFieldTypes.CONTAINER + } ] }; @@ -135,8 +141,14 @@ describe('FormModel', () => { fields: null, formDefinition: { fields: [ - { id: 'field1' }, - { id: 'field2' } + { + id: 'field1', + type: FormFieldTypes.CONTAINER + }, + { + id: 'field2', + type: FormFieldTypes.CONTAINER + } ] } }; @@ -164,10 +176,10 @@ describe('FormModel', () => { { id: 'tab2' } ], fields: [ - { id: 'field1', tab: 'tab1' }, - { id: 'field2', tab: 'tab2' }, - { id: 'field3', tab: 'tab1' }, - { id: 'field4', tab: 'missing-tab' } + { id: 'field1', tab: 'tab1', type: FormFieldTypes.CONTAINER }, + { id: 'field2', tab: 'tab2', type: FormFieldTypes.CONTAINER }, + { id: 'field3', tab: 'tab1', type: FormFieldTypes.DYNAMIC_TABLE }, + { id: 'field4', tab: 'missing-tab', type: FormFieldTypes.DYNAMIC_TABLE } ] }; @@ -227,7 +239,7 @@ describe('FormModel', () => { let form = new FormModel(json, data); expect(form.fields.length).toBe(1); - let container = form.fields[0]; + let container = <ContainerModel> form.fields[0]; expect(container.columns.length).toBe(2); let column1 = container.columns[0]; 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 index 00f72226f5..003b2cfc62 100644 --- 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 @@ -15,11 +15,14 @@ * limitations under the License. */ -import { FormWidgetModelCache } from './form-widget.model'; +import { FormWidgetModel, 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'; +import { FormFieldModel } from './form-field.model'; +import { FormFieldTypes } from './form-field-types'; +import { DynamicTableModel } from './dynamic-table.model'; export class FormModel { @@ -27,39 +30,25 @@ export class FormModel { 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; + readonly id: string; + readonly name: string; + readonly taskId: string; + readonly taskName: string = FormModel.UNSET_TASK_NAME; + processDefinitionId: string; + private _isValid: boolean = true; - get id(): string { - return this._id; - } - - get name(): string { - return this._name; - } - - get taskId(): string { - return this._taskId; - } - - get taskName(): string { - return this._taskName; + get isValid(): boolean { + return this._isValid; } readOnly: boolean = false; tabs: TabModel[] = []; - fields: ContainerModel[] = []; + fields: FormWidgetModel[] = []; outcomes: FormOutcomeModel[] = []; values: FormValues = {}; - private _json: any; - - get json() { - return this._json; - } + readonly json: any; hasTabs(): boolean { return this.tabs && this.tabs.length > 0; @@ -75,13 +64,15 @@ export class FormModel { 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; + 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; + this.processDefinitionId = json.processDefinitionId; let tabCache: FormWidgetModelCache<TabModel> = {}; @@ -91,7 +82,7 @@ export class FormModel { return model; }); - this.fields = this.parseContainerFields(json); + this.fields = this.parseRootFields(json); if (data) { this.loadData(data); @@ -102,7 +93,7 @@ export class FormModel { if (field.tab) { let tab = tabCache[field.tab]; if (tab) { - tab.fields.push(new ContainerModel(this, field.json)); + tab.fields.push(field); } } } @@ -117,9 +108,58 @@ export class FormModel { ); } } + this.validateForm(); } - private parseContainerFields(json: any): ContainerModel[] { + onFormFieldChanged(field: FormFieldModel) { + this.validateField(field); + } + + // TODO: consider evaluating and caching once the form is loaded + getFormFields(): FormFieldModel[] { + let result: FormFieldModel[] = []; + + for (let i = 0; i < this.fields.length; i++) { + let field = this.fields[i]; + + if (field.type === FormFieldTypes.CONTAINER || field.type === FormFieldTypes.GROUP) { + let container = <ContainerModel> field; + result.push(...container.getFormFields()); + } + + if (field.type === FormFieldTypes.DYNAMIC_TABLE) { + let dynamicTable = <DynamicTableModel> field; + result.push(dynamicTable.field); + } + } + + return result; + } + + private validateForm() { + this._isValid = true; + let fields = this.getFormFields(); + for (let i = 0; i < fields.length; i++) { + if (!fields[i].validate()) { + this._isValid = false; + return; + } + } + } + + private validateField(field: FormFieldModel) { + if (!field) { + return; + } + if (!field.validate()) { + this._isValid = false; + return; + } + this.validateForm(); + } + + // Activiti supports 2 types of root fields: 'container' and 'dynamic-table'. + private parseRootFields(json: any): FormWidgetModel[] { let fields = []; if (json.fields) { @@ -128,23 +168,34 @@ export class FormModel { fields = json.formDefinition.fields; } - return fields.map(obj => new ContainerModel(this, obj)); + let result: FormWidgetModel[] = []; + + for (let field of fields) { + if (field.type === FormFieldTypes.CONTAINER || field.type === FormFieldTypes.GROUP ) { + result.push(new ContainerModel(this, field)); + } else if (field.type === FormFieldTypes.DYNAMIC_TABLE) { + result.push(new DynamicTableModel(this, field)); + } else if (field.type === FormFieldTypes.DISPLAY_VALUE) { + // workaround for dynamic table on a completed/readonly form + if (field.params) { + let originalField = field.params['field']; + if (originalField.type === FormFieldTypes.DYNAMIC_TABLE) { + result.push(new DynamicTableModel(this, field)); + } + } + } + } + + return result; } // 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]; - } - } + for (let field of this.getFormFields()) { + 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/group-user.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.spec.ts new file mode 100644 index 0000000000..6c78b24266 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.spec.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 { GroupUserModel } from './group-user.model'; + +describe('GroupUserModel', () => { + + it('should init with json', () => { + let json = { + company: '<company>', + email: '<email>', + firstName: '<firstName>', + id: '<id>', + lastName: '<lastName>' + }; + + let model = new GroupUserModel(json); + expect(model.company).toBe(json.company); + expect(model.email).toBe(json.email); + expect(model.firstName).toBe(json.firstName); + expect(model.id).toBe(json.id); + expect(model.lastName).toBe(json.lastName); + }); + +}); diff --git a/ng2-components/ng2-activiti-processlist/src/models/user.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts similarity index 68% rename from ng2-components/ng2-activiti-processlist/src/models/user.model.ts rename to ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts index 6d21ad4e0b..66ee9f1154 100644 --- a/ng2-components/ng2-activiti-processlist/src/models/user.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts @@ -15,24 +15,21 @@ * limitations under the License. */ -/** - * - * This object represent the user. - * - * - * @returns {User} . - */ +export class GroupUserModel { -export class User { - id: number; + company: string; email: string; firstName: string; + id: string; lastName: string; - constructor(id: number, email: string, firstName: string, lastName: string) { - this.id = id; - this.email = email; - this.firstName = firstName; - this.lastName = lastName; + constructor(json?: any) { + if (json) { + this.company = json.company; + this.email = json.email; + this.firstName = json.firstName; + this.id = json.id; + this.lastName = json.lastName; + } } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts new file mode 100644 index 0000000000..bf14a9d2a7 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.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. + */ + +export class GroupModel { + + externalId: string; + groups: any; + id: string; + name: string; + status: string; + + constructor(json?: any) { + if (json) { + this.externalId = json.externalId; + this.groups = json.groups; + this.id = json.id; + this.name = json.name; + this.status = json.status; + } + } + +} 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 index 7a28d27c75..902340e910 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/index.ts @@ -27,3 +27,7 @@ export * from './container.model'; export * from './tab.model'; export * from './form-outcome.model'; export * from './form-outcome-event.model'; +export * from './form-field-validator'; +export * from './dynamic-table.model'; +export * from './dynamic-table-column'; +export * from './dynamic-table-row'; 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 index 6d060bc61f..8b270c6af3 100644 --- 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 @@ -15,7 +15,6 @@ * 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'; @@ -32,6 +31,7 @@ describe('TabModel', () => { let model = new TabModel(null, json); expect(model.id).toBe(json.id); expect(model.title).toBe(json.title); + expect(model.isVisible).toBe(true); expect(model.visibilityCondition).toBe(json.visibilityCondition); }); @@ -39,6 +39,8 @@ describe('TabModel', () => { let model = new TabModel(null, null); expect(model.id).toBeUndefined(); expect(model.title).toBeUndefined(); + expect(model.isVisible).toBeDefined(); + expect(model.isVisible).toBe(true); expect(model.visibilityCondition).toBeUndefined(); }); 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 index e1932ba1f6..2a6945e076 100644 --- 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 @@ -16,16 +16,16 @@ */ import { FormWidgetModel } from './form-widget.model'; -import { ContainerModel } from './container.model'; import { FormModel } from './form.model'; +import { WidgetVisibilityModel } from '../../../models/widget-visibility.model'; export class TabModel extends FormWidgetModel { - id: string; title: string; - visibilityCondition: any; + isVisible: boolean = true; + visibilityCondition: WidgetVisibilityModel; - fields: ContainerModel[] = []; + fields: FormWidgetModel[] = []; hasContent(): boolean { return this.fields && this.fields.length > 0; @@ -35,9 +35,8 @@ export class TabModel extends FormWidgetModel { super(form, json); if (json) { - this.id = json.id; this.title = json.title; - this.visibilityCondition = json.visibilityCondition; + this.visibilityCondition = <WidgetVisibilityModel> json.visibilityCondition; } } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.css new file mode 100644 index 0000000000..c8d5774494 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.css @@ -0,0 +1,23 @@ +.date-widget { + width: 100%; +} + +.date-widget--button { + margin-top: 15px; +} + +.date-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.date-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.date-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.date-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.html new file mode 100644 index 0000000000..cd2c2cb8b5 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.html @@ -0,0 +1,25 @@ +<div class="mdl-grid" *ngIf="field?.isVisible" id="data-widget"> + <div class="mdl-cell mdl-cell--11-col"> + <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label date-widget" + [class.date-widget__invalid]="!field.isValid"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [attr.required]="isRequired()" + [(ngModel)]="field.value" + (ngModelChange)="onDateChanged()" + (onOk)="onDateSelected()" + [disabled]="field.readOnly"> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}} (d-M-yyyy)</label> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> + </div> + </div> + <div class="mdl-cell mdl-cell--1-col"> + <button + class="mdl-button mdl-js-button mdl-button--icon date-widget--button" + (click)="datePicker.toggle()"> + <i class="material-icons">date_range</i> + </button> + </div> +</div> + diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.spec.ts new file mode 100644 index 0000000000..8b890fd6c9 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.spec.ts @@ -0,0 +1,271 @@ +/*! + * @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 { ElementRef } from '@angular/core'; +import { DateWidget } from './date.widget'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormModel } from './../core/form.model'; +import { CoreModule } from 'ng2-alfresco-core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; + +describe('DateWidget', () => { + + let widget: DateWidget; + let nativeElement: any; + let elementRef: ElementRef; + + beforeEach(() => { + nativeElement = { + querySelector: function () { + return null; + } + }; + elementRef = new ElementRef(nativeElement); + widget = new DateWidget(elementRef); + let componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']); + window['componentHandler'] = componentHandler; + }); + + it('should setup basic date picker settings on init ', () => { + expect(widget.datePicker).toBeUndefined(); + widget.ngOnInit(); + expect(widget.datePicker).toBeDefined(); + }); + + it('should setup min value for date picker', () => { + let minValue = '13-03-1982'; + widget.field = new FormFieldModel(null, { + minValue: minValue + }); + widget.ngOnInit(); + + let expected = moment(minValue, widget.DATE_FORMAT); + expect(widget.datePicker._past.isSame(expected)).toBeTruthy(); + }); + + it('should setup max value for date picker', () => { + let maxValue = '31-03-1982'; + widget.field = new FormFieldModel(null, { + maxValue: maxValue + }); + widget.ngOnInit(); + + let expected = moment(maxValue, widget.DATE_FORMAT); + expect(widget.datePicker._future.isSame(expected)).toBeTruthy(); + }); + + it('should setup default time value for date picker', () => { + let dateValue = '13-03-1982'; + widget.field = new FormFieldModel(null, { + type: 'date', + value: '1982-03-13' + }); + widget.ngOnInit(); + + let expected = moment(dateValue, widget.DATE_FORMAT); + expect(widget.datePicker.time.isSame(expected)).toBeTruthy(); + }); + + it('should setup trigger element', () => { + let el = {}; + spyOn(nativeElement, 'querySelector').and.returnValue(el); + widget.field = new FormFieldModel(null, {id: 'fake-id'}); + widget.ngOnInit(); + widget.ngAfterViewChecked(); + expect(widget.datePicker.trigger).toBe(el); + }); + + it('should not setup trigger element', () => { + let w = new DateWidget(null); + w.ngOnInit(); + expect(w.datePicker.trigger).toBeFalsy(); + }); + + it('should eval visibility on date changed', () => { + spyOn(widget, 'checkVisibility').and.callThrough(); + + let field = new FormFieldModel(null); + widget.field = field; + + widget.onDateChanged(); + expect(widget.checkVisibility).toHaveBeenCalledWith(field); + }); + + it('should update picker value on input date changed', () => { + widget.field = new FormFieldModel(null, { + type: 'date', + value: '13-03-1982' + }); + widget.ngOnInit(); + widget.field.value = '31-03-1982'; + widget.onDateChanged(); + + let expected = moment('31-03-1982', widget.DATE_FORMAT); + expect(widget.datePicker.time.isSame(expected)).toBeTruthy(); + }); + + it('should update field value on date selected', () => { + widget.field = new FormFieldModel(null, {type: 'date'}); + widget.ngOnInit(); + + let date = '13-3-1982'; + widget.datePicker.time = moment(date, widget.DATE_FORMAT); + widget.onDateSelected(); + expect(widget.field.value).toBe(date); + }); + + it('should update material textfield on date selected', () => { + spyOn(widget, 'setupMaterialTextField').and.callThrough(); + + widget.field = new FormFieldModel(null, {type: 'date'}); + widget.ngOnInit(); + + widget.datePicker.time = moment(); + widget.onDateSelected(); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should not update material textfield on date selected', () => { + let w = new DateWidget(null); + spyOn(w, 'setupMaterialTextField').and.callThrough(); + + w.field = new FormFieldModel(null, {type: 'date'}); + w.ngOnInit(); + + w.datePicker.time = moment(); + w.onDateSelected(); + expect(w.setupMaterialTextField).not.toHaveBeenCalled(); + }); + + it('should send field change event when a new date is picked from data picker', (done) => { + let w = new DateWidget(null); + spyOn(w, 'setupMaterialTextField').and.callThrough(); + w.field = new FormFieldModel(null, {value: '9-9-9999', type: 'date'}); + w.ngOnInit(); + w.datePicker.time = moment('9-9-9999', w.DATE_FORMAT); + w.fieldChanged.subscribe((field) => { + expect(field).toBeDefined(); + expect(field).not.toBeNull(); + expect(field.value).toEqual('9-9-9999'); + done(); + }); + w.onDateSelected(); + }); + + it('should send field change event when date is changed in input text', (done) => { + let w = new DateWidget(null); + spyOn(w, 'setupMaterialTextField').and.callThrough(); + w.field = new FormFieldModel(null, {value: '9-9-9999', type: 'date'}); + w.ngOnInit(); + w.datePicker.time = moment('9-9-9999', w.DATE_FORMAT); + w.fieldChanged.subscribe((field) => { + expect(field).toBeDefined(); + expect(field).not.toBeNull(); + expect(field.value).toEqual('9-9-9999'); + done(); + }); + + w.onDateChanged(); + }); + + describe('template check', () => { + let dateWidget: DateWidget; + let fixture: ComponentFixture<DateWidget>; + let element: HTMLElement; + let componentHandler; + + beforeEach(async(() => { + componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']); + window['componentHandler'] = componentHandler; + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [DateWidget] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(DateWidget); + dateWidget = fixture.componentInstance; + element = fixture.nativeElement; + }); + })); + + beforeEach(() => { + spyOn(dateWidget, 'setupMaterialTextField').and.stub(); + dateWidget.field = new FormFieldModel(new FormModel(), { + id: 'date-field-id', + name: 'date-name', + value: '9-9-9999', + type: 'date', + readOnly: 'false' + }); + dateWidget.field.isVisible = true; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + it('should show visible date widget', async(() => { + fixture.whenStable() + .then(() => { + expect(element.querySelector('#date-field-id')).toBeDefined(); + expect(element.querySelector('#date-field-id')).not.toBeNull(); + let dateElement: any = element.querySelector('#date-field-id'); + expect(dateElement.value).toEqual('9-9-9999'); + }); + })); + + it('should hide not visible date widget', async(() => { + dateWidget.field.isVisible = false; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + fixture.detectChanges(); + expect(element.querySelector('#data-widget')).toBeNull(); + }); + })); + + it('should become visibile if the visibility change to true', async(() => { + dateWidget.field.isVisible = false; + fixture.detectChanges(); + dateWidget.fieldChanged.subscribe((field) => { + field.isVisible = true; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#date-field-id')).toBeDefined(); + expect(element.querySelector('#date-field-id')).not.toBeNull(); + let dateElement: any = element.querySelector('#date-field-id'); + expect(dateElement.value).toEqual('9-9-9999'); + }); + }); + dateWidget.checkVisibility(dateWidget.field); + })); + + it('should be hided if the visibility change to false', async(() => { + dateWidget.fieldChanged.subscribe((field) => { + field.isVisible = false; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#data-widget')).toBeNull(); + }); + }); + dateWidget.checkVisibility(dateWidget.field); + })); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.ts new file mode 100644 index 0000000000..297f0b5fc3 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.ts @@ -0,0 +1,87 @@ +/*! + * @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, ElementRef, OnInit, AfterViewChecked } from '@angular/core'; +import { TextFieldWidgetComponent } from './../textfield-widget.component'; + +@Component({ + moduleId: module.id, + selector: 'date-widget', + templateUrl: './date.widget.html', + styleUrls: ['./date.widget.css'] +}) +export class DateWidget extends TextFieldWidgetComponent implements OnInit, AfterViewChecked { + + DATE_FORMAT: string = 'D-M-YYYY'; + + datePicker: any; + + constructor(elementRef: ElementRef) { + super(elementRef); + } + + ngOnInit() { + + let settings: any = { + type: 'date', + past: moment().subtract(100, 'years'), + future: moment().add(100, 'years') + }; + + if (this.field) { + + if (this.field.minValue) { + settings.past = moment(this.field.minValue, this.DATE_FORMAT); + } + + if (this.field.maxValue) { + settings.future = moment(this.field.maxValue, this.DATE_FORMAT); + } + + if (this.field.value) { + settings.init = moment(this.field.value, this.DATE_FORMAT); + } + } + + this.datePicker = new mdDateTimePicker.default(settings); + } + + ngAfterViewChecked() { + if (this.elementRef) { + let dataLocator = '#' + this.field.id; + this.datePicker.trigger = this.elementRef.nativeElement.querySelector(dataLocator); + } + } + + onDateChanged() { + if (this.field.value) { + this.datePicker.time = moment(this.field.value, this.DATE_FORMAT); + } + this.checkVisibility(this.field); + } + + onDateSelected() { + let newValue = this.datePicker.time.format(this.DATE_FORMAT); + this.field.value = newValue; + this.checkVisibility(this.field); + + if (this.elementRef) { + this.setupMaterialTextField(this.elementRef, componentHandler, newValue); + } + } + +} 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 index 2f39c56db3..e95d6aecdb 100644 --- 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 @@ -18,11 +18,8 @@ import { Component } from '@angular/core'; import { WidgetComponent } from './../widget.component'; -declare let __moduleName: string; -declare var componentHandler; - @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'display-text-widget', templateUrl: './display-text.widget.html', styleUrls: ['./display-text.widget.css'] 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 index c3b7a552c9..1fdcc3c541 100644 --- 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 @@ -1,3 +1,31 @@ .display-value-widget { width: 100%; } + +.display-value-widget__dynamic-table { + padding: 8px; +} + +.display-value-widget__dynamic-table table { + width: 100%; +} + +.upload-widget { + width:100%; + word-break: break-all; +} + +.upload-widget__icon { + float: left; + color: rgba(0,0,0,.26); +} + +.upload-widget__file { + float: left; + margin-top: 4px; + color: rgba(0,0,0,.26); +} + +.upload-widget__label { + color: rgba(0,0,0,.26); +} 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 index a5d637d91c..cbcd9b815d 100644 --- 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 @@ -1,10 +1,83 @@ -<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 [ngSwitch]="fieldType" class="display-value-widget"> + <div *ngSwitchCase="'boolean'"> + <label class="mdl-checkbox mdl-js-checkbox" [attr.for]="field.id"> + <input type="checkbox" + [attr.id]="field.id" + [(ngModel)]="value" + class="mdl-checkbox__input" + disabled> + <span class="mdl-checkbox__label">{{field.name}}</span> + </label> + </div> + <div *ngSwitchCase="'text'" + class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label text-widget"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [value]="value" + disabled> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + </div> + <div *ngSwitchCase="'multi-line-text'" + class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label multiline-text-widget"> + <textarea class="mdl-textfield__input" + type="text" + rows= "3" + [value]="value" + [attr.id]="field.id" + disabled> + </textarea> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + </div> + <div *ngSwitchCase="'hyperlink'" class="hyperlink-widget"> + <div> + <span>{{field.name}}</span> + </div> + <div> + <a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a> + </div> + </div> + <div *ngSwitchCase="'dynamic-table'"> + <div class="display-value-widget__dynamic-table"> + <div>{{field.name}}</div> + <table class="mdl-data-table mdl-js-data-table"> + <thead> + <tr> + <th *ngFor="let column of visibleColumns" + class="mdl-data-table__cell--non-numeric"> + {{column.name}} + </th> + </tr> + </thead> + <tbody> + <tr *ngFor="let row of rows"> + <td *ngFor="let column of visibleColumns" + class="mdl-data-table__cell--non-numeric"> + {{ getCellValue(row, column) }} + </td> + </tr> + </tbody> + </table> + </div> + </div> + <div *ngSwitchCase="'upload'"> + <div class="upload-widget"> + <label class="upload-widget__label" [attr.for]="field.id">{{field.name}}</label> + <div> + <i *ngIf="hasFile" class="material-icons upload-widget__icon">attachment</i> + <span *ngIf="hasFile" class="upload-widget__file">{{value}}</span> + </div> + </div> + </div> + <div *ngSwitchDefault + class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label text-widget"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [value]="value" + (ngModelChange)="checkVisibility(field)" + disabled> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + </div> </div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.spec.ts new file mode 100644 index 0000000000..6466a6c143 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.spec.ts @@ -0,0 +1,673 @@ +/*! + * @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 { DisplayValueWidget } from './display-value.widget'; +import { FormService } from '../../../services/form.service'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; +import { FormModel } from '../core/form.model'; +import { DynamicTableRow } from './../core/dynamic-table-row'; +import { DynamicTableColumn } from './../core/dynamic-table-column'; + +describe('DisplayValueWidget', () => { + + let widget: DisplayValueWidget; + let formService: FormService; + + beforeEach(() => { + formService = new FormService(null, null); + widget = new DisplayValueWidget(formService); + }); + + it('should require field to setup default value', () => { + widget.field = null; + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should take field value on init', () => { + let value = '<value>'; + widget.field = new FormFieldModel(null, { value: value }); + widget.field.params = null; + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + + it('should setup [BOOLEAN] field', () => { + expect(widget.value).toBeUndefined(); + + // test TRUE value + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 'true', + params: { + field: { + type: FormFieldTypes.BOOLEAN + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeTruthy(); + + // test FALSE value + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 'false', + params: { + field: { + type: FormFieldTypes.BOOLEAN + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeFalsy(); + }); + + it ('should setup [FUNCTIONAL-GROUP] field', () => { + let groupName: '<group>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: { + name: groupName + }, + params: { + field: { + type: FormFieldTypes.FUNCTIONAL_GROUP + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe(groupName); + }); + + it('should not setup [FUNCTIONAL-GROUP] field when missing value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.FUNCTIONAL_GROUP + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeNull(); + }); + + it('should setup [PEOPLE] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: { + firstName: 'John', + lastName: 'Doe' + }, + params: { + field: { + type: FormFieldTypes.PEOPLE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('John Doe'); + }); + + it('should not setup [PEOPLE] field whem missing value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.PEOPLE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should setup [UPLOAD] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: [ + { name: 'file1' } + ], + params: { + field: { + type: FormFieldTypes.UPLOAD + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('file1'); + }); + + it('should not setup [UPLOAD] field when missing value', () => { + widget.field = new FormFieldModel(null, { + value: null, + params: { + field: { + type: FormFieldTypes.UPLOAD + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeNull(); + }); + + it('should not setup [UPLOAD] field when empty value', () => { + widget.field = new FormFieldModel(null, { + value: [], + params: { + field: { + type: FormFieldTypes.UPLOAD + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeNull(); + }); + + it('should setup [TYPEAHEAD] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.TYPEAHEAD + } + } + }); + spyOn(widget, 'loadRestFieldValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRestFieldValue).toHaveBeenCalled(); + }); + + it('should setup [DROPDOWN] field with REST config', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.DROPDOWN + } + } + }); + spyOn(widget, 'loadRestFieldValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRestFieldValue).toHaveBeenCalled(); + }); + + it('should setup [RADIO_BUTTONS] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + spyOn(widget, 'loadRadioButtonValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRadioButtonValue).toHaveBeenCalled(); + }); + + it('should setup [RADIO_BUTTONS] value by options', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + value: '2', + options: [ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ], + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('option 2'); + }); + + it('should not setup [RADIO_BUTTONS] value with missing option', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + value: '100', + options: [ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ], + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('100'); + }); + + it('should not setup [RADIO_BUTTONS] when missing options', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + value: '100', + options: null, + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.field.options = null; + widget.ngOnInit(); + expect(widget.value).toBe('100'); + }); + + it('should setup [RADIO_BUTTONS] field with REST config', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + spyOn(widget, 'loadRestFieldValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRestFieldValue).toHaveBeenCalled(); + }); + + it('should setup rest field values with REST options', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next([ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ]); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '2', + options: [ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ], + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBe('option 2'); + }); + + it('should not setup rest field values with missing REST option', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next([ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ]); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '100', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBe('100'); + }); + + it('should not setup rest field values with no REST response', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '100', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBe('100'); + }); + + it('should handle rest error', () => { + const error = 'ERROR'; + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.throw(error) + ); + + spyOn(console, 'log').and.stub(); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '100', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith(error); + expect(widget.value).toBe('100'); + }); + + it('should setup [DATE] field with valid date', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: '1982-03-13T00:00:00.000Z', + params: { + field: { + type: FormFieldTypes.DATE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('13-3-1982'); + }); + + it('should setup [DATE] field with invalid date', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: '<invalid value>', + params: { + field: { + type: FormFieldTypes.DATE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('<invalid value>'); + }); + + it('should not setup [DATE] field when missing value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.DATE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should setup [AMOUNT] field with default currency', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 11, + params: { + field: { + type: FormFieldTypes.AMOUNT + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('$ 11'); + }); + + it('should setup [AMOUNT] field with custom currency', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 12.6, + currency: 'UAH', + params: { + field: { + type: FormFieldTypes.AMOUNT + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('UAH 12.6'); + }); + + it('should not setup [AMOUNT] field when missing value', () => { + widget.field = new FormFieldModel(null, { + params: { + field: { + type: FormFieldTypes.AMOUNT + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should setup [HYPERLINK] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + hyperlinkUrl: 'www.some-url.com', + displayText: 'Custom URL', + params: { + field: { + type: FormFieldTypes.HYPERLINK + } + } + }); + widget.ngOnInit(); + expect(widget.linkUrl).toBe(`http://${widget.field.hyperlinkUrl}`); + expect(widget.linkText).toBe(widget.field.displayText); + }); + + it('should take default value for unknown field type', () => { + const value = '<value>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: value, + params: { + field: { + type: '<unknown type>' + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + + it('should take default value when missing params', () => { + const value = '<value>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: value + }); + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + + it('should take default value when missing enclosed field type', () => { + const value = '<value>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: value, + params: { + field: { + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + + it('should setup [DYNAMIC_TABLE] field', () => { + let columns = [{ id: '1', visible: false }, { id: '2', visible: true }]; + let rows = [{}, {}]; + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.DYNAMIC_TABLE + } + }, + columnDefinitions: columns, + value: rows + }); + widget.ngOnInit(); + + expect(widget.columns.length).toBe(2); + expect(widget.columns[0].id).toBe(columns[0].id); + expect(widget.columns[1].id).toBe(columns[1].id); + + expect(widget.visibleColumns.length).toBe(1); + expect(widget.visibleColumns[0].id).toBe(columns[1].id); + + expect(widget.rows.length).toBe(2); + }); + + it('should setup [DYNAMIC_TABLE] field with empty schema', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.DYNAMIC_TABLE + } + }, + columnDefinitions: null, + value: null + }); + widget.ngOnInit(); + + expect(widget.value).toBeNull(); + expect(widget.columns).toEqual([]); + expect(widget.rows).toEqual([]); + }); + + it('should retrieve default cell value', () => { + const value = '<value>'; + let row = <DynamicTableRow> { value: { key: value } }; + let column = <DynamicTableColumn> { id: 'key' }; + + expect(widget.getCellValue(row, column)).toBe(value); + }); + + it('should retrieve dropdown cell value', () => { + const value = { id: '1', name: 'one' }; + let row = <DynamicTableRow> { value: { key: value } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Dropdown' }; + + expect(widget.getCellValue(row, column)).toBe(value.name); + }); + + it('should fallback to empty cell value for dropdown', () => { + let row = <DynamicTableRow> { value: {} }; + let column = <DynamicTableColumn> { id: 'key', type: 'Dropdown' }; + + expect(widget.getCellValue(row, column)).toBe(''); + }); + + it('should retrieve boolean cell value', () => { + let row1 = <DynamicTableRow> { value: { key: true } }; + let row2 = <DynamicTableRow> { value: { key: 'positive' } }; + let row3 = <DynamicTableRow> { value: { key: null } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Boolean' }; + + expect(widget.getCellValue(row1, column)).toBe(true); + expect(widget.getCellValue(row2, column)).toBe(true); + expect(widget.getCellValue(row3, column)).toBe(false); + }); + + it('should retrieve date cell value', () => { + const value = '2016-10-04T00:00:00.000Z'; + let row = <DynamicTableRow> { value: { key: value } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Date' }; + + expect(widget.getCellValue(row, column)).toBe('4-10-2016'); + }); + + it('should fallback to empty cell value for date', () => { + let row = <DynamicTableRow> { value: {} }; + let column = <DynamicTableColumn> { id: 'key', type: 'Date' }; + + expect(widget.getCellValue(row, column)).toBe(''); + }); + + it('should retrieve empty text cell value', () => { + let row = <DynamicTableRow> { value: {} }; + let column = <DynamicTableColumn> { id: 'key' }; + + expect(widget.getCellValue(row, column)).toBe(''); + }); + + it('should prepend default amount currency', () => { + const value = '10'; + let row = <DynamicTableRow> { value: { key: value } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Amount' }; + + const expected = `$ ${value}`; + expect(widget.getCellValue(row, column)).toBe(expected); + }); + + it('should prepend custom amount currency', () => { + const value = '10'; + const currency = 'GBP'; + let row = <DynamicTableRow> { value: { key: value } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Amount', amountCurrency: currency }; + + const expected = `${currency} ${value}`; + expect(widget.getCellValue(row, column)).toBe(expected); + }); + + it('should use zero for missing amount', () => { + const value = null; + const currency = 'GBP'; + let row = <DynamicTableRow> { value: { key: value } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Amount', amountCurrency: currency }; + + const expected = `${currency} 0`; + expect(widget.getCellValue(row, column)).toBe(expected); + }); + +}); 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 index a095b85024..d41159be74 100644 --- 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 @@ -15,18 +15,211 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { WidgetComponent } from './../widget.component'; - -declare let __moduleName: string; -declare var componentHandler; +import { FormFieldTypes } from '../core/form-field-types'; +import { FormService } from '../../../services/form.service'; +import { FormFieldOption } from './../core/form-field-option'; +import { DynamicTableColumn } from './../core/dynamic-table-column'; +import { DynamicTableRow } from './../core/dynamic-table-row'; @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'display-value-widget', templateUrl: './display-value.widget.html', styleUrls: ['./display-value.widget.css'] }) -export class DisplayValueWidget extends WidgetComponent { +export class DisplayValueWidget extends WidgetComponent implements OnInit { + value: any; + fieldType: string; + + // hyperlink + linkUrl: string; + linkText: string; + + // dynamic table + rows: DynamicTableRow[] = []; + columns: DynamicTableColumn[] = []; + visibleColumns: DynamicTableColumn[] = []; + + // upload/attach + hasFile: boolean = false; + + constructor(private formService: FormService) { + super(); + } + + ngOnInit() { + if (this.field) { + this.value = this.field.value; + + if (this.field.params) { + let originalField = this.field.params['field']; + if (originalField && originalField.type) { + this.fieldType = originalField.type; + switch (originalField.type) { + case FormFieldTypes.BOOLEAN: + this.value = this.field.value === 'true' ? true : false; + break; + case FormFieldTypes.FUNCTIONAL_GROUP: + if (this.field.value) { + this.value = this.field.value.name; + } else { + this.value = null; + } + break; + case FormFieldTypes.PEOPLE: + let model = this.field.value; + if (model) { + let displayName = `${model.firstName} ${model.lastName}`; + this.value = displayName.trim(); + } + break; + case FormFieldTypes.UPLOAD: + let files = this.field.value || []; + if (files.length > 0) { + this.value = decodeURI(files[0].name); + this.hasFile = true; + } else { + this.value = null; + this.hasFile = false; + } + break; + case FormFieldTypes.TYPEAHEAD: + this.loadRestFieldValue(); + break; + case FormFieldTypes.DROPDOWN: + this.loadRestFieldValue(); + break; + case FormFieldTypes.RADIO_BUTTONS: + if (this.field.restUrl) { + this.loadRestFieldValue(); + } else { + this.loadRadioButtonValue(); + } + break; + case FormFieldTypes.DATE: + if (this.value) { + let d = moment(this.value.split('T')[0], 'YYYY-M-D'); + if (d.isValid()) { + this.value = d.format('D-M-YYYY'); + } + } + break; + case FormFieldTypes.AMOUNT: + if (this.value) { + let currency = this.field.currency || '$'; + this.value = `${currency} ${this.field.value}`; + } + break; + case FormFieldTypes.HYPERLINK: + this.linkUrl = this.getHyperlinkUrl(this.field); + this.linkText = this.getHyperlinkText(this.field); + break; + case FormFieldTypes.DYNAMIC_TABLE: + let json = this.field.json; + if (json.columnDefinitions) { + this.columns = json.columnDefinitions.map(obj => <DynamicTableColumn> obj); + this.visibleColumns = this.columns.filter(col => col.visible); + } + if (json.value) { + this.rows = json.value.map(obj => <DynamicTableRow> {selected: false, value: obj}); + } + break; + default: + this.value = this.field.value; + break; + } + } + } + } + } + + loadRadioButtonValue() { + let options = this.field.options || []; + let toSelect = options.find(item => item.id === this.field.value); + if (toSelect) { + this.value = toSelect.name; + } else { + this.value = this.field.value; + } + } + + loadRestFieldValue() { + if (this.field.form.processDefinitionId) { + this.getValuesByProcessDefinitionId(); + } else { + this.getValuesByTaskId(); + } + } + + getValuesByProcessDefinitionId() { + this.formService + .getRestFieldValuesByProcessId( + this.field.form.processDefinitionId, + this.field.id + ) + .subscribe( + (result: FormFieldOption[]) => { + let options = result || []; + let toSelect = options.find(item => item.id === this.field.value); + if (toSelect) { + this.value = toSelect.name; + } else { + this.value = this.field.value; + } + }, + error => { + console.log(error); + this.value = this.field.value; + } + ); + } + + getValuesByTaskId() { + this.formService + .getRestFieldValues(this.field.form.taskId, this.field.id) + .subscribe( + (result: FormFieldOption[]) => { + let options = result || []; + let toSelect = options.find(item => item.id === this.field.value); + if (toSelect) { + this.value = toSelect.name; + } else { + this.value = this.field.value; + } + }, + error => { + console.log(error); + this.value = this.field.value; + } + ); + } + + getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any { + let result = row.value[column.id]; + + if (column.type === 'Dropdown') { + if (result) { + return result.name; + } + } + + if (column.type === 'Boolean') { + return result ? true : false; + } + + if (column.type === 'Date') { + if (result) { + return moment(result.split('T')[0], 'YYYY-MM-DD').format('D-M-YYYY'); + } + } + + if (column.type === 'Amount') { + return (column.amountCurrency || '$') + ' ' + (result || 0); + } + + return result || ''; + } } 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 index f1be2ed3ba..ae995ca854 100644 --- 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 @@ -2,6 +2,22 @@ width: 100%; } -.dropdown-widget > select { +.dropdown-widget__select { width: 100%; } + +.dropdown-widget__invalid .dropdown-widget__select { + border-color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label { + color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label:after { + background-color: #d50000; +} + +.dropdown-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} 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 index a6c3d50a94..5b8f73f170 100644 --- 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 @@ -1,6 +1,11 @@ -<div class="dropdown-widget"> - <label [attr.for]="field.id">{{field.name}}</label> - <select [(ngModel)]="field.value" (ngModelChange)="checkVisibility(field)"> +<div class="dropdown-widget" + [class.dropdown-widget__invalid]="!field.isValid"> + <label class="dropdown-widget__label" [attr.for]="field.id">{{field.name}}</label> + <select class="dropdown-widget__select" + [attr.id]="field.id" + [(ngModel)]="field.value" + (ngModelChange)="checkVisibility(field)"> <option *ngFor="let opt of field.options" [value]="opt.id">{{opt.name}}</option> </select> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> </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 index 2fdaf83374..7fb176b8c1 100644 --- 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 @@ -15,198 +15,87 @@ * 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 { FormService } from '../../../services/form.service'; import { DropdownWidget } from './dropdown.widget'; import { FormModel } from './../core/form.model'; import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldOption } from './../core/form-field-option'; describe('DropdownWidget', () => { - let http: Http; + let formService: FormService; let widget: DropdownWidget; beforeEach(() => { - http = <Http> { - get(url: string, options?: RequestOptionsArgs): Observable<Response> { - return null; - } - }; - widget = new DropdownWidget(http); + formService = new FormService(null, null); + widget = new DropdownWidget(formService); + widget.field = new FormFieldModel(new FormModel()); }); - it('should fetch and parse REST data on init', () => { + it('should require field with restUrl', () => { + spyOn(formService, 'getRestFieldValues').and.stub(); - 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(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); - // 2) Missing [optionType] - widget.field = new FormFieldModel(form, { - optionType: null, - restUrl: 'http://<address>', - restIdProperty: 'uid', - restLabelProperty: 'text' - }); + widget.field = new FormFieldModel(null, { restUrl: null }); 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(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); }); - it('should parse only array response', () => { - expect(widget.loadFromJson([])).toBeFalsy(); + it('should request field values from service', () => { + const taskId = '<form-id>'; + const fieldId = '<field-id>'; - widget.field = new FormFieldModel(new FormModel()); - expect(widget.loadFromJson([])).toBeTruthy(); + let form = new FormModel({ + taskId: taskId + }); - expect(widget.loadFromJson(null)).toBeFalsy(); - expect(widget.loadFromJson({})).toBeFalsy(); - }); + widget.field = new FormFieldModel(form, { + id: fieldId, + restUrl: '<url>' + }); - 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); + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next(null); observer.complete(); - }); - }); + }) + ); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId); + }); - let field = new FormFieldModel(new FormModel(), { - optionType: 'rest', - restUrl: 'http://<address>', - restIdProperty: 'uid.value', - restLabelProperty: 'name.fullName' - }); + it('should log error to console by default', () => { + spyOn(console, 'error').and.stub(); + widget.handleError('Err'); + expect(console.error).toHaveBeenCalledWith('Err'); + }); - widget.field = field; + it('should preserve empty option when loading fields', () => { + let restFieldValue: FormFieldOption = <FormFieldOption> { id: '1', name: 'Option1' }; + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next([restFieldValue]); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + let emptyOption: FormFieldOption = <FormFieldOption> { id: 'empty', name: 'Empty' }; + widget.field = new FormFieldModel(form, { + id: '<id>', + restUrl: '/some/url/address', + hasEmptyValue: true, + options: [emptyOption] + }); 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); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.field.options.length).toBe(2); + expect(widget.field.options[0]).toBe(emptyOption); + expect(widget.field.options[1]).toBe(restFieldValue); }); - - 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 index 5e47fd41b9..721bccf4da 100644 --- 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 @@ -16,70 +16,72 @@ */ import { Component, OnInit } from '@angular/core'; -import { Http } from '@angular/http'; -import { ObjectUtils } from 'ng2-alfresco-core'; +import { FormService } from '../../../services/form.service'; import { WidgetComponent } from './../widget.component'; - -declare let __moduleName: string; -declare var componentHandler; +import { FormFieldOption } from './../core/form-field-option'; @Component({ - moduleId: __moduleName, + moduleId: module.id, 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) { + constructor(private formService: FormService) { super(); } ngOnInit() { - if (this.field && - this.field.optionType === 'rest' && - this.field.restUrl && - this.field.restIdProperty && - this.field.restLabelProperty) { + if (this.field && this.field.restUrl) { + if (this.field.form.processDefinitionId) { + this.getValuesByProcessDefinitionId(); + } else { + this.getValuesByTaskId(); + } + } + } - let url = `${this.field.restUrl}`; - this.http.get(url).subscribe( - response => { - let json: any = response.json(); - this.loadFromJson(json); + getValuesByTaskId() { + this.formService + .getRestFieldValues( + this.field.form.taskId, + this.field.id + ) + .subscribe( + (result: FormFieldOption[]) => { + let options = []; + if (this.field.emptyOption) { + options.push(this.field.emptyOption); + } + this.field.options = options.concat((result || [])); + this.field.updateForm(); }, 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; + getValuesByProcessDefinitionId() { + this.formService + .getRestFieldValuesByProcessId( + this.field.form.processDefinitionId, + this.field.id + ) + .subscribe( + (result: FormFieldOption[]) => { + let options = []; + if (this.field.emptyOption) { + options.push(this.field.emptyOption); + } + this.field.options = options.concat((result || [])); + this.field.updateForm(); + }, + this.handleError + ); } - 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); + console.error(error); } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.css new file mode 100644 index 0000000000..758c2d71bc --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.css @@ -0,0 +1,38 @@ +.dynamic-table-widget { + padding: 8px; +} + +.dynamic-table-widget__row-selected, +.dynamic-table-widget__row-selected:hover { + background-color: #eef !important; +} + +.dynamic-table-widget__table-container { + overflow-y: auto; + width: 100%; +} + +.dynamic-table-widget__table { + width: 100%; +} + +.dynamic-table-widget__invalid .dynamic-table-widget__label { + color: #d50000; +} + +.dynamic-table-widget__invalid .dynamic-table-widget__table-container { + border: 1px solid #d50000; +} + +.dynamic-table-widget__invalid .dynamic-table-widget__table { + border: none; +} + +.dynamic-table-widget__summary { + visibility: hidden; + color: #d50000; +} + +.dynamic-table-widget__invalid .dynamic-table-widget__summary { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.html new file mode 100644 index 0000000000..541312c836 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.html @@ -0,0 +1,67 @@ +<div class="dynamic-table-widget" + [class.dynamic-table-widget__invalid]="!isValid()"> + <div class="dynamic-table-widget__label">{{content.name}}</div> + + <div *ngIf="!editMode"> + <div class="dynamic-table-widget__table-container"> + <table class="mdl-data-table mdl-js-data-table dynamic-table-widget__table"> + <thead> + <tr> + <th *ngFor="let column of content.visibleColumns" + class="mdl-data-table__cell--non-numeric"> + {{column.name}} + </th> + </tr> + </thead> + <tbody> + <tr *ngFor="let row of content.rows" + [class.dynamic-table-widget__row-selected]="row.selected"> + <td *ngFor="let column of content.visibleColumns" + class="mdl-data-table__cell--non-numeric" + (click)="onRowClicked(row)"> + {{ getCellValue(row, column) }} + </td> + </tr> + </tbody> + </table> + </div> + + <div> + <button class="mdl-button mdl-js-button mdl-button--icon" + [disabled]="!hasSelection()" + (click)="moveSelectionUp()"> + <i class="material-icons">arrow_upward</i> + </button> + <button class="mdl-button mdl-js-button mdl-button--icon" + [disabled]="!hasSelection()" + (click)="moveSelectionDown()"> + <i class="material-icons">arrow_downward</i> + </button> + <button class="mdl-button mdl-js-button mdl-button--icon" + (click)="addNewRow()"> + <i class="material-icons">add_circle_outline</i> + </button> + <button class="mdl-button mdl-js-button mdl-button--icon" + [disabled]="!hasSelection()" + (click)="deleteSelection()"> + <i class="material-icons">remove_circle_outline</i> + </button> + <button class="mdl-button mdl-js-button mdl-button--icon" + [disabled]="!hasSelection()" + (click)="editSelection()"> + <i class="material-icons">edit</i> + </button> + </div> + </div> + + <row-editor *ngIf="editMode" + [table]="content" + [row]="editRow" + [column]="column" + (save)="onSaveChanges()" + (cancel)="onCancelChanges()"> + </row-editor> + + <div class="dynamic-table-widget__summary">{{content?.field.validationSummary}}</div> + +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.spec.ts new file mode 100644 index 0000000000..c5cbdc9ecb --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.spec.ts @@ -0,0 +1,251 @@ +/*! + * @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 { DynamicTableWidget } from './dynamic-table.widget'; +import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, FormModel, FormFieldTypes } from './../core/index'; + +describe('DynamicTableWidget', () => { + + let widget: DynamicTableWidget; + let table: DynamicTableModel; + + beforeEach(() => { + table = new DynamicTableModel(null); + widget = new DynamicTableWidget(null); + widget.content = table; + }); + + it('should select row on click', () => { + let row = <DynamicTableRow> { selected: false }; + widget.onRowClicked(row); + + expect(row.selected).toBeTruthy(); + expect(widget.content.selectedRow).toBe(row); + }); + + it('should requre table to select clicked row', () => { + let row = <DynamicTableRow> { selected: false }; + widget.content = null; + widget.onRowClicked(row); + + expect(row.selected).toBeFalsy(); + }); + + it('should reset selected row', () => { + let row = <DynamicTableRow> { selected: false }; + widget.content.rows.push(row); + widget.content.selectedRow = row; + expect(widget.content.selectedRow).toBe(row); + expect(row.selected).toBeTruthy(); + + widget.onRowClicked(null); + expect(widget.content.selectedRow).toBeNull(); + expect(row.selected).toBeFalsy(); + }); + + it('should check selection', () => { + let row = <DynamicTableRow> { selected: false }; + widget.content.rows.push(row); + widget.content.selectedRow = row; + expect(widget.hasSelection()).toBeTruthy(); + + widget.content.selectedRow = null; + expect(widget.hasSelection()).toBeFalsy(); + + widget.content = null; + expect(widget.hasSelection()).toBeFalsy(); + }); + + it('should require table to move selection up', () => { + widget.content = null; + expect(widget.moveSelectionUp()).toBeFalsy(); + }); + + it('should move selection up', () => { + let row1 = <DynamicTableRow> {}; + let row2 = <DynamicTableRow> {}; + widget.content.rows.push(...[row1, row2]); + widget.content.selectedRow = row2; + + expect(widget.moveSelectionUp()).toBeTruthy(); + expect(widget.content.rows.indexOf(row2)).toBe(0); + }); + + it('should require table to move selection down', () => { + widget.content = null; + expect(widget.moveSelectionDown()).toBeFalsy(); + }); + + it('should move selection down', () => { + let row1 = <DynamicTableRow> { }; + let row2 = <DynamicTableRow> { }; + widget.content.rows.push(...[row1, row2]); + widget.content.selectedRow = row1; + + expect(widget.moveSelectionDown()).toBeTruthy(); + expect(widget.content.rows.indexOf(row1)).toBe(1); + }); + + it('should require table to delete selection', () => { + widget.content = null; + expect(widget.deleteSelection()).toBeFalsy(); + }); + + it('should delete selected row', () => { + let row = <DynamicTableRow> {}; + widget.content.rows.push(row); + widget.content.selectedRow = row; + widget.deleteSelection(); + expect(widget.content.rows.length).toBe(0); + }); + + it('should require table to add new row', () => { + widget.content = null; + expect(widget.addNewRow()).toBeFalsy(); + }); + + it('should start editing new row', () => { + expect(widget.editMode).toBeFalsy(); + expect(widget.editRow).toBeNull(); + + expect(widget.addNewRow()).toBeTruthy(); + expect(widget.editRow).not.toBeNull(); + expect(widget.editMode).toBeTruthy(); + }); + + it('should require table to edit selected row', () => { + widget.content = null; + expect(widget.editSelection()).toBeFalsy(); + }); + + it('should start editing selected row', () => { + expect(widget.editMode).toBeFalsy(); + expect(widget.editRow).toBeFalsy(); + + let row = <DynamicTableRow> { value: true }; + widget.content.selectedRow = row; + + expect(widget.editSelection()).toBeTruthy(); + expect(widget.editMode).toBeTruthy(); + expect(widget.editRow).not.toBeNull(); + expect(widget.editRow.value).toEqual(row.value); + }); + + it('should copy row', () => { + let row = <DynamicTableRow> { value: { opt: { key: '1', value: 1 } } }; + let copy = widget.copyRow(row); + expect(copy.value).toEqual(row.value); + }); + + it('should require table to retrieve cell value', () => { + widget.content = null; + expect(widget.getCellValue(null, null)).toBeNull(); + }); + + it('should retrieve cell value', () => { + const value = '<value>'; + let row = <DynamicTableRow> { value: { key: value } }; + let column = <DynamicTableColumn> { id: 'key' }; + + expect(widget.getCellValue(row, column)).toBe(value); + }); + + it('should save changes and add new row', () => { + let row = <DynamicTableRow> { isNew: true, value: { key: 'value' } }; + widget.editMode = true; + widget.editRow = row; + + widget.onSaveChanges(); + + expect(row.isNew).toBeFalsy(); + expect(widget.content.selectedRow).toBeNull(); + expect(widget.content.rows.length).toBe(1); + expect(widget.content.rows[0].value).toEqual(row.value); + }); + + it('should save changes and update row', () => { + let row = <DynamicTableRow> { isNew: false, value: { key: 'value' } }; + widget.editMode = true; + widget.editRow = row; + widget.content.selectedRow = row; + + widget.onSaveChanges(); + expect(widget.content.selectedRow.value).toEqual(row.value); + }); + + it('should require table to save changes', () => { + spyOn(console, 'log').and.stub(); + widget.editMode = true; + widget.content = null; + widget.onSaveChanges(); + + expect(widget.editMode).toBeFalsy(); + expect(console.log).toHaveBeenCalledWith(widget.ERROR_MODEL_NOT_FOUND); + }); + + it('should cancel changes', () => { + widget.editMode = true; + widget.editRow = <DynamicTableRow> {}; + widget.onCancelChanges(); + + expect(widget.editMode).toBeFalsy(); + expect(widget.editRow).toBeNull(); + }); + + it('should be valid by default', () => { + widget.content.field = null; + expect(widget.isValid()).toBeTruthy(); + + widget.content = null; + expect(widget.isValid()).toBeTruthy(); + }); + + it('should take validation state from underlying field', () => { + let form = new FormModel(); + widget.content = new DynamicTableModel(form, { + type: FormFieldTypes.DYNAMIC_TABLE, + required: true, + value: null + }); + + expect(widget.content.field.validate()).toBeFalsy(); + expect(widget.isValid()).toBe(widget.content.field.isValid); + expect(widget.content.field.isValid).toBeFalsy(); + + widget.content.field.value = [{}]; + + expect(widget.content.field.validate()).toBeTruthy(); + expect(widget.isValid()).toBe(widget.content.field.isValid); + expect(widget.content.field.isValid).toBeTruthy(); + + }); + + it('should prepend default currency for amount columns', () => { + let row = <DynamicTableRow> { value: { key: '100' } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Amount' }; + let actual = widget.getCellValue(row, column); + expect(actual).toBe('$ 100'); + }); + + it('should prepend custom currency for amount columns', () => { + let row = <DynamicTableRow> { value: { key: '100' } }; + let column = <DynamicTableColumn> { id: 'key', type: 'Amount', amountCurrency: 'GBP' }; + let actual = widget.getCellValue(row, column); + expect(actual).toBe('GBP 100'); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.ts new file mode 100644 index 0000000000..14bbf832f1 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/dynamic-table.widget.ts @@ -0,0 +1,161 @@ +/*! + * @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, ElementRef } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; +import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../core/index'; + +@Component({ + moduleId: module.id, + selector: 'dynamic-table-widget', + templateUrl: './dynamic-table.widget.html', + styleUrls: ['./dynamic-table.widget.css'] +}) +export class DynamicTableWidget extends WidgetComponent { + + ERROR_MODEL_NOT_FOUND = 'Table model not found'; + + @Input() + content: DynamicTableModel; + + editMode: boolean = false; + editRow: DynamicTableRow = null; + + constructor(private elementRef: ElementRef) { + super(); + } + + isValid() { + let result = true; + + if (this.content && this.content.field) { + result = this.content.field.isValid; + } + + return result; + } + + onRowClicked(row: DynamicTableRow) { + if (this.content) { + this.content.selectedRow = row; + } + } + + hasSelection(): boolean { + return !!(this.content && this.content.selectedRow); + } + + moveSelectionUp(): boolean { + if (this.content) { + this.content.moveRow(this.content.selectedRow, -1); + return true; + } + return false; + } + + moveSelectionDown(): boolean { + if (this.content) { + this.content.moveRow(this.content.selectedRow, 1); + return true; + } + return false; + } + + deleteSelection(): boolean { + if (this.content) { + this.content.deleteRow(this.content.selectedRow); + return true; + } + return false; + } + + addNewRow(): boolean { + if (this.content) { + this.editRow = <DynamicTableRow> { + isNew: true, + selected: false, + value: {} + }; + this.editMode = true; + return true; + } + return false; + } + + editSelection(): boolean { + if (this.content) { + this.editRow = this.copyRow(this.content.selectedRow); + this.editMode = true; + return true; + } + return false; + } + + getCellValue(row: DynamicTableRow, column: DynamicTableColumn): any { + if (this.content) { + let result = this.content.getCellValue(row, column); + if (column.type === 'Amount') { + return (column.amountCurrency || '$') + ' ' + (result || 0); + } + return result; + } + return null; + } + + onSaveChanges() { + if (this.content) { + if (this.editRow.isNew) { + let row = this.copyRow(this.editRow); + this.content.selectedRow = null; + this.content.addRow(row); + this.editRow.isNew = false; + } else { + this.content.selectedRow.value = this.copyObject(this.editRow.value); + } + this.content.flushValue(); + } else { + this.handleError(this.ERROR_MODEL_NOT_FOUND); + } + this.editMode = false; + } + + onCancelChanges() { + this.editMode = false; + this.editRow = null; + } + + copyRow(row: DynamicTableRow): DynamicTableRow { + return <DynamicTableRow> { + value: this.copyObject(row.value) + }; + } + + private copyObject(obj: any): any { + let result = obj; + + if (typeof obj === 'object' && obj !== null && obj !== undefined) { + result = Object.assign({}, obj); + Object.keys(obj).forEach(key => { + if (typeof obj[key] === 'object') { + result[key] = this.copyObject(obj[key]); + } + }); + } + + return result; + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.html new file mode 100644 index 0000000000..b92d6f30ba --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.html @@ -0,0 +1,11 @@ + <label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" [attr.for]="column.id"> + <input + class="mdl-checkbox__input" + type="checkbox" + [attr.id]="column.id" + [checked]="table.getCellValue(row, column)" + [required]="column.required" + [disabled]="!column.editable" + (change)="onValueChanged(row, column, $event)"> + <span class="mdl-checkbox__label">{{column.name}}</span> +</label> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.spec.ts new file mode 100644 index 0000000000..052b64914d --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.spec.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. + */ + +import { BooleanEditorComponent } from './boolean.editor'; +import { DynamicTableRow, DynamicTableColumn } from './../../../core/index'; + +describe('BooleanEditorComponent', () => { + + let component: BooleanEditorComponent; + + beforeEach(() => { + component = new BooleanEditorComponent(); + }); + + it('should update row value on change', () => { + let row = <DynamicTableRow> { value: {} }; + let column = <DynamicTableColumn> { id: 'key' }; + let event = { target: { checked: true } }; + + component.onValueChanged(row, column, event); + expect(row.value[column.id]).toBeTruthy(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.ts new file mode 100644 index 0000000000..eb65f03598 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/boolean/boolean.editor.ts @@ -0,0 +1,35 @@ +/*! + * @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 { CellEditorComponent } from './../cell.editor'; +import { DynamicTableRow, DynamicTableColumn } from './../../../core/index'; + +@Component({ + moduleId: module.id, + selector: 'alf-boolean-editor', + templateUrl: './boolean.editor.html', + styleUrls: ['./boolean.editor.css'] +}) +export class BooleanEditorComponent extends CellEditorComponent { + + onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) { + let value: boolean = (<HTMLInputElement>event.target).checked; + row.value[column.id] = value; + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.spec.ts new file mode 100644 index 0000000000..239ce25eee --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.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 { CellEditorComponent } from './cell.editor'; + +describe('CellEditorComponent', () => { + + class CustomEditor extends CellEditorComponent { + onError(error: any) { + this.handleError(error); + } + } + + let component: CustomEditor; + + beforeEach(() => { + component = new CustomEditor(); + }); + + it('should handle error', () => { + const error = 'error'; + spyOn(console, 'error').and.stub(); + + component.onError(error); + expect(console.error).toHaveBeenCalledWith(error); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.ts new file mode 100644 index 0000000000..ed3bbab266 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/cell.editor.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 { Input } from '@angular/core'; +import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../../core/index'; + +export abstract class CellEditorComponent { + + @Input() + table: DynamicTableModel; + + @Input() + row: DynamicTableRow; + + @Input() + column: DynamicTableColumn; + + handleError(error: any) { + console.error(error); + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.css new file mode 100644 index 0000000000..9101330a83 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.css @@ -0,0 +1,7 @@ +.date-editor { + width: 100%; +} + +.date-editor--button { + margin-top: 15px; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.html new file mode 100644 index 0000000000..982a5e67bc --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.html @@ -0,0 +1,23 @@ +<div class="mdl-grid"> + <div class="mdl-cell mdl-cell--11-col"> + <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label date-editor"> + <input id="dateInput" + class="mdl-textfield__input" + type="text" + [value]="value" + [attr.id]="column.id" + [required]="column.required" + [disabled]="!column.editable" + (keyup)="onDateChanged($event)" + (onOk)="onDateSelected($event)"> + <label class="mdl-textfield__label" [attr.for]="column.id">{{column.name}} (d-M-yyyy)</label> + </div> + </div> + <div *ngIf="column.editable" class="mdl-cell mdl-cell--1-col"> + <button + class="mdl-button mdl-js-button mdl-button--icon date-editor--button" + (click)="datePicker.toggle()"> + <i class="material-icons">date_range</i> + </button> + </div> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.spec.ts new file mode 100644 index 0000000000..cdfd64229f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.spec.ts @@ -0,0 +1,168 @@ +/*! + * @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 { ElementRef } from '@angular/core'; +import { DateEditorComponent } from './date.editor'; +import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../../../core/index'; + +describe('DateEditorComponent', () => { + + let nativeElement: any; + let elementRef: ElementRef; + let component: DateEditorComponent; + let row: DynamicTableRow; + let column: DynamicTableColumn; + let table: DynamicTableModel; + + beforeEach(() => { + nativeElement = { + querySelector: function () { return null; } + }; + + row = <DynamicTableRow> { value: { date: '1879-03-14T00:00:00.000Z' } }; + column = <DynamicTableColumn> { id: 'date', type: 'Date' }; + table = new DynamicTableModel(null); + table.rows.push(row); + table.columns.push(column); + + elementRef = new ElementRef(nativeElement); + component = new DateEditorComponent(elementRef); + component.table = table; + component.row = row; + component.column = column; + }); + + it('should setup date picker on init', () => { + let trigger = {}; + spyOn(nativeElement, 'querySelector').and.returnValue(trigger); + + component.ngOnInit(); + + let settings = component.settings; + expect(settings.type).toBe('date'); + expect(settings.future.year()).toBe(moment().year() + 100); + expect(settings.init.isSame(moment('14-03-1879', component.DATE_FORMAT))).toBeTruthy(); + expect(component.datePicker.trigger).toBe(trigger); + }); + + it('should require cell value to setup initial date', () => { + row.value[column.id] = null; + component.ngOnInit(); + expect(component.settings.init).toBeUndefined(); + }); + + it('should require dom element to setup trigger', () => { + component = new DateEditorComponent(null); + component.table = table; + component.row = row; + component.column = column; + component.ngOnInit(); + expect(component.datePicker.trigger).toBeFalsy(); + }); + + it('should update fow value on change', () => { + component.ngOnInit(); + component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY'); + component.onDateSelected(null); + expect(row.value[column.id]).toBe('1879-03-14T00:00:00.000Z'); + }); + + it('should update material textfield on date selected', () => { + component.ngOnInit(); + component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY'); + spyOn(component, 'updateMaterialTextField').and.stub(); + component.onDateSelected(null); + expect(component.updateMaterialTextField).toHaveBeenCalled(); + }); + + it('should require dom element to update material textfield on change', () => { + component = new DateEditorComponent(null); + component.table = table; + component.row = row; + component.column = column; + component.ngOnInit(); + + component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY'); + spyOn(component, 'updateMaterialTextField').and.stub(); + component.onDateSelected(null); + expect(component.updateMaterialTextField).not.toHaveBeenCalled(); + }); + + it('should require dom element to update material textfield', () => { + let result = component.updateMaterialTextField(null, 'value'); + expect(result).toBeFalsy(); + }); + + it('should require native dom element to update material textfield', () => { + elementRef.nativeElement = null; + let result = component.updateMaterialTextField(elementRef, 'value'); + expect(result).toBeFalsy(); + }); + + it('should require input element to update material textfield', () => { + spyOn(nativeElement, 'querySelector').and.returnValue(null); + let result = component.updateMaterialTextField(elementRef, 'value'); + expect(result).toBeFalsy(); + }); + + it('should update material textfield with new value', () => { + let called = false; + const value = '<value>'; + + spyOn(nativeElement, 'querySelector').and.returnValue({ + MaterialTextfield: { + change: function (val) { + called = true; + expect(val).toBe(value); + } + } + }); + component.updateMaterialTextField(elementRef, value); + expect(called).toBeTruthy(); + }); + + it('should update picker when input changed', () => { + const input = '14-03-2016'; + let event = { target: { value: input } }; + component.ngOnInit(); + component.onDateChanged(event); + + expect(component.datePicker.time.isSame(moment(input, 'DD-MM-YYYY'))).toBeTruthy(); + }); + + it('should update row value upon user input', () => { + const input = '14-03-2016'; + let event = { target: { value: input } }; + + component.ngOnInit(); + component.onDateChanged(event); + + let actual = row.value[column.id]; + expect(actual).toBe('2016-03-14T00:00:00.000Z'); + }); + + it('should flush value on user input', () => { + spyOn(table, 'flushValue').and.callThrough(); + let event = { target: { value: 'value' } }; + + component.ngOnInit(); + component.onDateChanged(event); + + expect(table.flushValue).toHaveBeenCalled(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.ts new file mode 100644 index 0000000000..fd20cbd321 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/date/date.editor.ts @@ -0,0 +1,89 @@ +/*! + * @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, ElementRef } from '@angular/core'; +import { CellEditorComponent } from './../cell.editor'; + +@Component({ + moduleId: module.id, + selector: 'alf-date-editor', + templateUrl: './date.editor.html', + styleUrls: ['./date.editor.css'] +}) +export class DateEditorComponent extends CellEditorComponent implements OnInit { + + DATE_FORMAT: string = 'DD-MM-YYYY'; + + datePicker: any; + settings: any; + value: any; + + constructor(private elementRef: ElementRef) { + super(); + } + + ngOnInit() { + this.settings = { + type: 'date', + past: moment().subtract(100, 'years'), + future: moment().add(100, 'years') + }; + + this.value = this.table.getCellValue(this.row, this.column); + if (this.value) { + this.settings.init = moment(this.value, this.DATE_FORMAT); + } + + this.datePicker = new mdDateTimePicker.default(this.settings); + if (this.elementRef) { + this.datePicker.trigger = this.elementRef.nativeElement.querySelector('#dateInput'); + } + } + + onDateChanged(event: any) { + let newValue = (<HTMLInputElement> event.target).value; + let dateValue = moment(newValue, this.DATE_FORMAT); + this.datePicker.time = dateValue; + this.row.value[this.column.id] = `${dateValue.format('YYYY-MM-DD')}T00:00:00.000Z`; + this.table.flushValue(); + }; + + onDateSelected(event: CustomEvent) { + this.value = this.datePicker.time.format('DD-MM-YYYY'); + let newValue = this.datePicker.time.format('YYYY-MM-DD'); + this.row.value[this.column.id] = `${newValue}T00:00:00.000Z`; + this.table.flushValue(); + + if (this.elementRef) { + this.updateMaterialTextField(this.elementRef, newValue); + } + } + + updateMaterialTextField(elementRef: ElementRef, value: string): boolean { + if (elementRef) { + let el = elementRef.nativeElement; + if (el) { + let container = el.querySelector('.mdl-textfield'); + if (container) { + container.MaterialTextfield.change(value); + return true; + } + } + } + return false; + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.css new file mode 100644 index 0000000000..0db743bfa6 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.css @@ -0,0 +1,3 @@ +.dropdown-editor__select { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.html new file mode 100644 index 0000000000..026f9cd555 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.html @@ -0,0 +1,14 @@ +<div class="dropdown-editor"> + <label [attr.for]="column.id">{{column.name}}</label> + <div> + <select + class="dropdown-editor__select" + [value]="value" + [required]="column.required" + [disabled]="!column.editable" + (change)="onValueChanged(row, column, $event)"> + <option></option> + <option *ngFor="let opt of options" [value]="opt.name">{{opt.name}}</option> + </select> + </div> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts new file mode 100644 index 0000000000..71e0335523 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts @@ -0,0 +1,154 @@ +/*! + * @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 { DropdownEditorComponent } from './dropdown.editor'; +import { + DynamicTableModel, + DynamicTableRow, + DynamicTableColumn, + DynamicTableColumnOption, + FormFieldModel, + FormModel +} from './../../../core/index'; +import { FormService } from './../../../../../services/form.service'; + +describe('DropdownEditorComponent', () => { + + let component: DropdownEditorComponent; + let formService: FormService; + let form: FormModel; + let table: DynamicTableModel; + let column: DynamicTableColumn; + let row: DynamicTableRow; + + beforeEach(() => { + formService = new FormService(null, null); + + row = <DynamicTableRow> { value: { dropdown: 'one' } }; + column = <DynamicTableColumn> { + id: 'dropdown', + options: [ + <DynamicTableColumnOption> { id: '1', name: 'one' }, + <DynamicTableColumnOption> { id: '2', name: 'two' } + ] + }; + + table = new DynamicTableModel(null); + form = new FormModel({ taskId: '<task-id>' }); + table.field = new FormFieldModel(form, { id: '<field-id>' }); + table.rows.push(row); + table.columns.push(column); + + component = new DropdownEditorComponent(formService); + component.table = table; + component.row = row; + component.column = column; + }); + + it('should require table field to setup', () => { + table.field = null; + component.ngOnInit(); + expect(component.value).toBeNull(); + expect(component.options).toEqual([]); + }); + + it('should setup with manual mode', () => { + row.value[column.id] = 'two'; + component.ngOnInit(); + expect(component.options).toEqual(column.options); + expect(component.value).toBe(row.value[column.id]); + }); + + it('should setup empty columns for manual mode', () => { + column.options = null; + component.ngOnInit(); + expect(component.options).toEqual([]); + }); + + it('should setup with REST mode', () => { + column.optionType = 'rest'; + row.value[column.id] = 'twelve'; + + let restResults = [ + <DynamicTableColumnOption> { id: '11', name: 'eleven' }, + <DynamicTableColumnOption> { id: '12', name: 'twelve' } + ]; + + spyOn(formService, 'getRestFieldValuesColumn').and.returnValue( + Observable.create(observer => { + observer.next(restResults); + observer.complete(); + }) + ); + + component.ngOnInit(); + + expect(formService.getRestFieldValuesColumn).toHaveBeenCalledWith( + form.taskId, + table.field.id, + column.id + ); + + expect(column.options).toEqual(restResults); + expect(component.options).toEqual(restResults); + expect(component.value).toBe(row.value[column.id]); + }); + + it('should create empty options array on REST response', () => { + column.optionType = 'rest'; + + spyOn(formService, 'getRestFieldValuesColumn').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); + + component.ngOnInit(); + + expect(formService.getRestFieldValuesColumn).toHaveBeenCalledWith( + form.taskId, + table.field.id, + column.id + ); + + expect(column.options).toEqual([]); + expect(component.options).toEqual([]); + expect(component.value).toBe(row.value[column.id]); + }); + + it('should handle REST error', () => { + column.optionType = 'rest'; + const error = 'error'; + + spyOn(formService, 'getRestFieldValuesColumn').and.returnValue( + Observable.throw(error) + ); + spyOn(component, 'handleError').and.stub(); + + component.ngOnInit(); + expect(component.handleError).toHaveBeenCalledWith(error); + }); + + it('should update row on value change', () => { + let event = { target: { value: 'two' } }; + component.onValueChanged(row, column, event); + expect(row.value[column.id]).toBe(column.options[1]); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.ts new file mode 100644 index 0000000000..640855ed81 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/dropdown/dropdown.editor.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. + */ + +import { Component, OnInit } from '@angular/core'; +import { CellEditorComponent } from './../cell.editor'; +import { DynamicTableRow, DynamicTableColumn, DynamicTableColumnOption } from './../../../core/index'; +import { FormService } from './../../../../../services/form.service'; + +@Component({ + moduleId: module.id, + selector: 'alf-dropdown-editor', + templateUrl: './dropdown.editor.html', + styleUrls: ['./dropdown.editor.css'] +}) +export class DropdownEditorComponent extends CellEditorComponent implements OnInit { + + value: any = null; + options: DynamicTableColumnOption[] = []; + + constructor(private formService: FormService) { + super(); + } + + ngOnInit() { + let field = this.table.field; + if (field) { + if (this.column.optionType === 'rest') { + if (this.table.form && this.table.form.processDefinitionId) { + this.getValuesByProcessDefinitionId(field); + } else { + this.getValuesByTaskId(field); + } + } else { + this.options = this.column.options || []; + this.value = this.table.getCellValue(this.row, this.column); + } + } + } + + getValuesByTaskId(field) { + this.formService + .getRestFieldValuesColumn( + field.form.taskId, + field.id, + this.column.id + ) + .subscribe( + (result: DynamicTableColumnOption[]) => { + this.column.options = result || []; + this.options = this.column.options; + this.value = this.table.getCellValue(this.row, this.column); + }, + err => this.handleError(err) + ); + } + + getValuesByProcessDefinitionId(field) { + this.formService + .getRestFieldValuesColumnByProcessId( + field.form.processDefinitionId, + field.id, + this.column.id + ) + .subscribe( + (result: DynamicTableColumnOption[]) => { + this.column.options = result || []; + this.options = this.column.options; + this.value = this.table.getCellValue(this.row, this.column); + }, + err => this.handleError(err) + ); + } + + onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) { + let value: any = (<HTMLInputElement>event.target).value; + value = column.options.find(opt => opt.name === value); + row.value[column.id] = value; + } +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.css new file mode 100644 index 0000000000..d72e472c43 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.css @@ -0,0 +1,16 @@ +.row-editor { + padding: 8px; +} + +.row-editor__validation-summary { + visibility: hidden; +} + +.row-editor__invalid .row-editor__validation-summary { + padding-left: 16px; + padding-right: 16px; + padding-top: 8px; + padding-bottom: 8px; + color: #d50000; + visibility: visible; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.html new file mode 100644 index 0000000000..4db9fed239 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.html @@ -0,0 +1,45 @@ +<div class="row-editor mdl-shadow--2dp" + [class.row-editor__invalid]="!validationSummary.isValid"> + <div class="mdl-grid" *ngFor="let column of table.columns"> + <div class="mdl-cell mdl-cell--6-col" [ngSwitch]="column.type"> + <div *ngSwitchCase="'Dropdown'"> + <alf-dropdown-editor + [table]="table" + [row]="row" + [column]="column"> + </alf-dropdown-editor> + </div> + <div *ngSwitchCase="'Date'"> + <alf-date-editor + [table]="table" + [row]="row" + [column]="column"> + </alf-date-editor> + </div> + + <div *ngSwitchCase="'Boolean'"> + <alf-boolean-editor + [table]="table" + [row]="row" + [column]="column"> + </alf-boolean-editor> + </div> + <div *ngSwitchDefault> + <alf-text-editor + [table]="table" + [row]="row" + [column]="column"> + </alf-text-editor> + </div> + </div> + </div> + <div class="row-editor__validation-summary" *ngIf="!validationSummary.isValid">{{validationSummary.text}}</div> + <div> + <button + class="mdl-button mdl-js-button mdl-js-ripple-effect" + (click)="onCancelChanges()">Cancel</button> + <button + class="mdl-button mdl-js-button mdl-js-ripple-effect" + (click)="onSaveChanges()">Save</button> + </div> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.spec.ts new file mode 100644 index 0000000000..c460d25fc5 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.spec.ts @@ -0,0 +1,76 @@ +/*! + * @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 { RowEditorComponent } from './row.editor'; +import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../../core/index'; + +describe('RowEditorComponent', () => { + + let component: RowEditorComponent; + + beforeEach(() => { + component = new RowEditorComponent(); + component.table = new DynamicTableModel(null); + component.row = <DynamicTableRow> {}; + component.column = <DynamicTableColumn> {}; + }); + + it('should be valid upon init', () => { + expect(component.validationSummary.isValid).toBeTruthy(); + expect(component.validationSummary.text).toBeNull(); + }); + + it('should emit [cancel] event', (done) => { + component.cancel.subscribe(e => { + expect(e.table).toBe(component.table); + expect(e.row).toBe(component.row); + expect(e.column).toBe(component.column); + done(); + }); + component.onCancelChanges(); + }); + + it('should validate row on save', () => { + spyOn(component.table, 'validateRow').and.callThrough(); + component.onSaveChanges(); + expect(component.table.validateRow).toHaveBeenCalledWith(component.row); + }); + + it('should emit [save] event', (done) => { + spyOn(component.table, 'validateRow').and.returnValue( + <DynamicRowValidationSummary> { isValid: true, text: null } + ); + component.save.subscribe(e => { + expect(e.table).toBe(component.table); + expect(e.row).toBe(component.row); + expect(e.column).toBe(component.column); + done(); + }); + component.onSaveChanges(); + }); + + it('should not emit [save] event for invalid row', () => { + spyOn(component.table, 'validateRow').and.returnValue( + <DynamicRowValidationSummary> { isValid: false, text: 'error' } + ); + let raised = false; + component.save.subscribe(e => raised = true); + component.onSaveChanges(); + expect(raised).toBeFalsy(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.ts new file mode 100644 index 0000000000..6ca74f59b8 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/row.editor.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 { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../../core/index'; + +@Component({ + moduleId: module.id, + selector: 'row-editor', + templateUrl: './row.editor.html', + styleUrls: ['./row.editor.css'] +}) +export class RowEditorComponent { + + @Input() + table: DynamicTableModel; + + @Input() + row: DynamicTableRow; + + @Input() + column: DynamicTableColumn; + + @Output() + save: EventEmitter<any> = new EventEmitter<any>(); + + @Output() + cancel: EventEmitter<any> = new EventEmitter<any>(); + + validationSummary: DynamicRowValidationSummary = <DynamicRowValidationSummary> { isValid: true, text: null }; + + onCancelChanges() { + this.cancel.emit({ + table: this.table, + row: this.row, + column: this.column + }); + } + + onSaveChanges() { + this.validate(); + if (this.isValid()) { + this.save.emit({ + table: this.table, + row: this.row, + column: this.column + }); + } + } + + private isValid(): boolean { + return this.validationSummary && this.validationSummary.isValid; + } + + private validate() { + this.validationSummary = this.table.validateRow(this.row); + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.css b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.css new file mode 100644 index 0000000000..81665765ce --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.css @@ -0,0 +1,3 @@ +.text-editor { + width: 100%; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.html b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.html new file mode 100644 index 0000000000..3e702fd673 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.html @@ -0,0 +1,11 @@ +<div alfresco-mdl-textfield class="text-editor"> + <input + class="mdl-textfield__input" + type="text" + [value]="table.getCellValue(row, column)" + (keyup)="onValueChanged(row, column, $event)" + [required]="column.required" + [disabled]="!column.editable" + [attr.id]="column.id"> + <label class="mdl-textfield__label" [attr.for]="column.id">{{displayName}}</label> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.spec.ts new file mode 100644 index 0000000000..7bfdfaf374 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.spec.ts @@ -0,0 +1,40 @@ +/*! + * @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 { TextEditorComponent } from './text.editor'; +import { DynamicTableRow, DynamicTableColumn } from './../../../core/index'; + +describe('TextEditorComponent', () => { + + let editor: TextEditorComponent; + + beforeEach(() => { + editor = new TextEditorComponent(); + }); + + it('should update row value on change', () => { + let row = <DynamicTableRow> { value: {} }; + let column = <DynamicTableColumn> { id: 'key' }; + + const value = '<value>'; + let event = { target: { value } }; + + editor.onValueChanged(row, column, event); + expect(row.value[column.id]).toBe(value); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.ts new file mode 100644 index 0000000000..8e0bdf35e3 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dynamic-table/editors/text/text.editor.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 { Component, OnInit } from '@angular/core'; +import { CellEditorComponent } from './../cell.editor'; +import { DynamicTableRow, DynamicTableColumn } from './../../../core/index'; + +@Component({ + moduleId: module.id, + selector: 'alf-text-editor', + templateUrl: './text.editor.html', + styleUrls: ['./text.editor.css'] +}) +export class TextEditorComponent extends CellEditorComponent implements OnInit { + + displayName: string; + + ngOnInit() { + this.displayName = this.table.getDisplayText(this.column); + } + + onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) { + let value: any = (<HTMLInputElement>event.target).value; + row.value[column.id] = value; + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css new file mode 100644 index 0000000000..8c9426d570 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css @@ -0,0 +1,49 @@ +.functional-group-widget { + width: 100%; +} + +.functional-group-widget--autocomplete { + background-color: #fff; + position: absolute; + z-index: 5; + color: #555; + margin: -15px 0 0 0; +} + +.functional-group-widget--autocomplete > ul { + list-style-type: none; + position: static; + + height: auto; + width: auto; + min-width: 124px; + padding: 8px 0; + margin: 0; + + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); + border-radius: 2px; +} + +.functional-group-widget--autocomplete > ul > li { + opacity: 1; +} + +.people-widget--autocomplete > ul > li { + opacity: 1; +} + +.functional-group-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.functional-group-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.functional-group-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.functional-group-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html new file mode 100644 index 0000000000..90090bfe4f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html @@ -0,0 +1,22 @@ +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label functional-group-widget" + [class.functional-group-widget__invalid]="!field.isValid"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [(ngModel)]="value" + (ngModelChange)="checkVisibility(field)" + (keyup)="onKeyUp($event)" + (blur)="onBlur()" + [disabled]="field.readOnly"> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> +</div> +<div class="functional-group-widget--autocomplete mdl-shadow--2dp" *ngIf="popupVisible && groups.length > 0"> + <ul> + <li *ngFor="let item of groups" + class="mdl-menu__item" + (click)="onItemClick(item, $event)"> + {{item.name}} + </li> + </ul> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts new file mode 100644 index 0000000000..e314372f74 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts @@ -0,0 +1,264 @@ +/*! + * @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 { ElementRef } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { FunctionalGroupWidget } from './functional-group.widget'; +import { FormService } from '../../../services/form.service'; +import { FormModel } from '../core/form.model'; +import { FormFieldModel } from '../core/form-field.model'; +import { GroupModel } from '../core/group.model'; + +describe('FunctionalGroupWidget', () => { + + let componentHandler; + let formService: FormService; + let elementRef: ElementRef; + let widget: FunctionalGroupWidget; + + beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + + formService = new FormService(null, null); + elementRef = new ElementRef(null); + widget = new FunctionalGroupWidget(formService, elementRef); + widget.field = new FormFieldModel(new FormModel()); + }); + + it('should setup text from underlying field on init', () => { + let group = new GroupModel({ name: 'group-1'}); + widget.field.value = group; + + spyOn(formService, 'getWorkflowGroups').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); + + widget.ngOnInit(); + expect(formService.getWorkflowGroups).toHaveBeenCalled(); + expect(widget.value).toBe(group.name); + }); + + it('should not setup text on init', () => { + widget.field.value = null; + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should require form field to setup values on init', () => { + widget.field = null; + widget.ngOnInit(); + + expect(widget.value).toBeUndefined(); + expect(widget.groupId).toBeUndefined(); + }); + + it('should setup group restriction', () => { + widget.ngOnInit(); + expect(widget.groupId).toBeUndefined(); + + widget.field.params = { restrictWithGroup: { id: '<id>' } }; + widget.ngOnInit(); + expect(widget.groupId).toBe('<id>'); + }); + + it('should flush value on blur', (done) => { + spyOn(widget, 'flushValue').and.stub(); + widget.onBlur(); + + setTimeout(() => { + expect(widget.flushValue).toHaveBeenCalled(); + done(); + }, 200); + }); + + it('should prevent default behaviour on option item click', () => { + let event = jasmine.createSpyObj('event', ['preventDefault']); + widget.onItemClick(null, event); + expect(event.preventDefault).toHaveBeenCalled(); + }); + + it('should update values on item click', () => { + let item = new GroupModel({ name: 'group-1' }); + + widget.onItemClick(item, null); + expect(widget.field.value).toBe(item); + expect(widget.value).toBe(item.name); + }); + + it('should hide popup on flush', () => { + widget.popupVisible = true; + widget.flushValue(); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should update form on value flush', () => { + spyOn(widget.field, 'updateForm').and.callThrough(); + widget.flushValue(); + expect(widget.field.updateForm).toHaveBeenCalled(); + }); + + it('should flush selected value', () => { + let groups: GroupModel[] = [ + new GroupModel({ id: '1', name: 'group 1' }), + new GroupModel({ id: '2', name: 'group 2' }) + ]; + + widget.groups = groups; + widget.value = 'group 2'; + widget.flushValue(); + + expect(widget.value).toBe(groups[1].name); + expect(widget.field.value).toBe(groups[1]); + }); + + it('should be case insensitive when flushing value', () => { + let groups: GroupModel[] = [ + new GroupModel({ id: '1', name: 'group 1' }), + new GroupModel({ id: '2', name: 'gRoUp 2' }) + ]; + + widget.groups = groups; + widget.value = 'GROUP 2'; + widget.flushValue(); + + expect(widget.value).toBe(groups[1].name); + expect(widget.field.value).toBe(groups[1]); + }); + + it('should hide popup on key up', () => { + widget.popupVisible = true; + widget.onKeyUp(null); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should fetch groups and show popup on key up', () => { + let groups: GroupModel[] = [ + new GroupModel(), + new GroupModel() + ]; + spyOn(formService, 'getWorkflowGroups').and.returnValue( + Observable.create(observer => { + observer.next(groups); + observer.complete(); + }) + ); + + widget.value = 'group'; + widget.onKeyUp(null); + + expect(formService.getWorkflowGroups).toHaveBeenCalledWith('group', undefined); + expect(widget.groups).toBe(groups); + expect(widget.popupVisible).toBeTruthy(); + }); + + it('should fetch groups with a group filter', () => { + let groups: GroupModel[] = [ + new GroupModel(), + new GroupModel() + ]; + spyOn(formService, 'getWorkflowGroups').and.returnValue( + Observable.create(observer => { + observer.next(groups); + observer.complete(); + }) + ); + + widget.groupId = 'parentGroup'; + widget.value = 'group'; + widget.onKeyUp(null); + + expect(formService.getWorkflowGroups).toHaveBeenCalledWith('group', 'parentGroup'); + expect(widget.groups).toBe(groups); + expect(widget.popupVisible).toBeTruthy(); + }); + + it('should hide popup when fetching empty group list', () => { + spyOn(formService, 'getWorkflowGroups').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); + + widget.value = 'group'; + widget.onKeyUp(null); + + expect(formService.getWorkflowGroups).toHaveBeenCalledWith('group', undefined); + expect(widget.groups.length).toBe(0); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should not fetch groups when value is missing', () => { + spyOn(formService, 'getWorkflowGroups').and.stub(); + + widget.value = null; + widget.onKeyUp(null); + + expect(formService.getWorkflowGroups).not.toHaveBeenCalled(); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should not fetch groups when value violates constraints', () => { + spyOn(formService, 'getWorkflowGroups').and.stub(); + + widget.minTermLength = 4; + widget.value = '123'; + widget.onKeyUp(null); + + expect(formService.getWorkflowGroups).not.toHaveBeenCalled(); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should setup mdl textfield on view init', () => { + spyOn(widget, 'setupMaterialComponents').and.callThrough(); + spyOn(widget, 'setupMaterialTextField').and.callThrough(); + + widget.value = '<value>'; + widget.ngAfterViewInit(); + + expect(widget.setupMaterialComponents).toHaveBeenCalledWith(componentHandler); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should require component handler to setup textfield', () => { + expect(widget.setupMaterialComponents(null)).toBeFalsy(); + }); + + it('should require element reference to setup textfield', () => { + let w = new FunctionalGroupWidget(formService, null); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeFalsy(); + + w = new FunctionalGroupWidget(formService, elementRef); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeTruthy(); + }); + + it('should require value to setup textfield', () => { + widget.value = '<value>'; + expect(widget.setupMaterialComponents(componentHandler)).toBeTruthy(); + + widget.value = null; + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts new file mode 100644 index 0000000000..e44e3cf185 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.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, OnInit, ElementRef } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; +import { FormService } from '../../../services/form.service'; +import { GroupModel } from './../core/group.model'; + +@Component({ + moduleId: module.id, + selector: 'functional-group-widget', + templateUrl: './functional-group.widget.html', + styleUrls: ['./functional-group.widget.css'] +}) +export class FunctionalGroupWidget extends WidgetComponent implements OnInit { + + value: string; + popupVisible: boolean = false; + groups: GroupModel[] = []; + minTermLength: number = 1; + groupId: string; + + constructor(private formService: FormService, + private elementRef: ElementRef) { + super(); + } + + // TODO: investigate, called 2 times + // https://github.com/angular/angular/issues/6782 + ngOnInit() { + if (this.field) { + let group = this.field.value; + if (group) { + this.value = group.name; + } + + let params = this.field.params; + if (params && params['restrictWithGroup']) { + let restrictWithGroup = <GroupModel> params['restrictWithGroup']; + this.groupId = restrictWithGroup.id; + } + + // Load auto-completion for previously saved value + if (this.value) { + this.formService + .getWorkflowGroups(this.value, this.groupId) + .subscribe((result: GroupModel[]) => this.groups = result || []); + } + } + } + + onKeyUp(event: KeyboardEvent) { + if (this.value && this.value.length >= this.minTermLength) { + this.formService.getWorkflowGroups(this.value, this.groupId) + .subscribe((result: GroupModel[]) => { + this.groups = result || []; + this.popupVisible = this.groups.length > 0; + }); + } else { + this.popupVisible = false; + } + } + + onBlur() { + setTimeout(() => { + this.flushValue(); + }, 200); + } + + flushValue() { + this.popupVisible = false; + + let option = this.groups.find(item => item.name.toLocaleLowerCase() === this.value.toLocaleLowerCase()); + + if (option) { + this.field.value = option; + this.value = option.name; + } else { + this.field.value = null; + this.value = null; + } + + this.field.updateForm(); + } + + // TODO: still causes onBlur execution + onItemClick(item: GroupModel, event: Event) { + if (item) { + this.field.value = item; + this.value = item.name; + } + if (event) { + event.preventDefault(); + } + } + + setupMaterialComponents(handler: any): boolean { + super.setupMaterialComponents(handler); + if (handler) { + if (this.elementRef && this.value) { + this.setupMaterialTextField(this.elementRef, handler, this.value); + return true; + } + } + return false; + } +} 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 index 68717afde2..f6bc91987a 100644 --- 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 @@ -1,3 +1,8 @@ <div class="hyperlink-widget"> - <a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a> + <div> + <span>{{field.name}}</span> + </div> + <div> + <a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a> + </div> </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 index 9b6be175ae..d6ce5d8d78 100644 --- 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 @@ -15,10 +15,10 @@ * 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'; +import { WidgetComponent } from './../widget.component'; describe('HyperlinkWidget', () => { @@ -34,6 +34,7 @@ describe('HyperlinkWidget', () => { widget.field = new FormFieldModel(new FormModel(), { displayText: text }); + widget.ngOnInit(); expect(widget.linkText).toBe(text); }); @@ -45,12 +46,15 @@ describe('HyperlinkWidget', () => { displayText: null, hyperlinkUrl: url }); + widget.ngOnInit(); expect(widget.linkText).toBe(url); }); it('should require field to get link text', () => { widget.field = null; + widget.ngOnInit(); + expect(widget.linkText).toBeNull(); }); @@ -59,21 +63,25 @@ describe('HyperlinkWidget', () => { displayText: null, hyperlinkUrl: null }); + widget.ngOnInit(); expect(widget.linkText).toBeNull(); }); it('should return default url for missing field', () => { widget.field = null; - expect(widget.linkUrl).toBe(HyperlinkWidget.DEFAULT_URL); + widget.ngOnInit(); + + expect(widget.linkUrl).toBe(WidgetComponent.DEFAULT_HYPERLINK_URL); }); it('should return default url for missing field property', () => { widget.field = new FormFieldModel(new FormModel(), { hyperlinkUrl: null }); + widget.ngOnInit(); - expect(widget.linkUrl).toBe(HyperlinkWidget.DEFAULT_URL); + expect(widget.linkUrl).toBe(WidgetComponent.DEFAULT_HYPERLINK_URL); }); it('should prepend url with scheme', () => { @@ -81,8 +89,9 @@ describe('HyperlinkWidget', () => { widget.field = new FormFieldModel(new FormModel(), { hyperlinkUrl: url }); + widget.ngOnInit(); - expect(widget.linkUrl).toBe(`${HyperlinkWidget.DEFAULT_URL_SCHEME}${url}`); + expect(widget.linkUrl).toBe(`${WidgetComponent.DEFAULT_HYPERLINK_SCHEME}${url}`); }); it('should not prepend url with scheme', () => { @@ -90,6 +99,7 @@ describe('HyperlinkWidget', () => { widget.field = new FormFieldModel(new FormModel(), { hyperlinkUrl: url }); + widget.ngOnInit(); 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 index dd9c7e3fd2..82983f9fd0 100644 --- 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 @@ -15,41 +15,25 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { WidgetComponent } from './../widget.component'; -declare let __moduleName: string; -declare var componentHandler; - @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'hyperlink-widget', templateUrl: './hyperlink.widget.html', styleUrls: ['./hyperlink.widget.css'] }) -export class HyperlinkWidget extends WidgetComponent { +export class HyperlinkWidget extends WidgetComponent implements OnInit { - static DEFAULT_URL: string = '#'; - static DEFAULT_URL_SCHEME: string = 'http://'; + linkUrl: string = WidgetComponent.DEFAULT_HYPERLINK_URL; + linkText: string = null; - 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 { + ngOnInit() { if (this.field) { - return this.field.displayText || this.field.hyperlinkUrl; + this.linkUrl = this.getHyperlinkUrl(this.field); + this.linkText = this.getHyperlinkText(this.field); } - 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 index 51ceb74e05..493174ea37 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/index.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/index.ts @@ -28,6 +28,18 @@ 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'; +import { AttachWidget } from './attach/attach.widget'; +import { TypeaheadWidget } from './typeahead/typeahead.widget'; +import { FunctionalGroupWidget } from './functional-group/functional-group.widget'; +import { PeopleWidget } from './people/people.widget'; +import { DateWidget } from './date/date.widget'; +import { AmountWidget } from './amount/amount.widget'; +import { DynamicTableWidget } from './dynamic-table/dynamic-table.widget'; +import { DateEditorComponent } from './dynamic-table/editors/date/date.editor'; +import { DropdownEditorComponent } from './dynamic-table/editors/dropdown/dropdown.editor'; +import { BooleanEditorComponent } from './dynamic-table/editors/boolean/boolean.editor'; +import { TextEditorComponent } from './dynamic-table/editors/text/text.editor'; +import { RowEditorComponent } from './dynamic-table/editors/row.editor'; // core export * from './widget.component'; @@ -48,13 +60,24 @@ 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 * from './attach/attach.widget'; +export * from './typeahead/typeahead.widget'; +export * from './functional-group/functional-group.widget'; +export * from './people/people.widget'; +export * from './date/date.widget'; +export * from './amount/amount.widget'; +export * from './dynamic-table/dynamic-table.widget'; -export const CONTAINER_WIDGET_DIRECTIVES: [any] = [ +// editors (dynamic table) +export * from './dynamic-table/editors/row.editor'; +export * from './dynamic-table/editors/date/date.editor'; +export * from './dynamic-table/editors/dropdown/dropdown.editor'; +export * from './dynamic-table/editors/boolean/boolean.editor'; +export * from './dynamic-table/editors/text/text.editor'; + +export const WIDGET_DIRECTIVES: any[] = [ TabsWidget, - ContainerWidget -]; - -export const PRIMITIVE_WIDGET_DIRECTIVES: [any] = [ + ContainerWidget, TextWidget, NumberWidget, CheckboxWidget, @@ -64,7 +87,18 @@ export const PRIMITIVE_WIDGET_DIRECTIVES: [any] = [ RadioButtonsWidget, DisplayValueWidget, DisplayTextWidget, - UploadWidget + UploadWidget, + AttachWidget, + TypeaheadWidget, + FunctionalGroupWidget, + PeopleWidget, + DateWidget, + AmountWidget, + + DynamicTableWidget, + DateEditorComponent, + DropdownEditorComponent, + BooleanEditorComponent, + TextEditorComponent, + RowEditorComponent ]; - - 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 index 9c7ec9fb37..006fdf8d96 100644 --- 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 @@ -1,3 +1,19 @@ -:host .multiline-text-widget { +.multiline-text-widget { width: 100%; } + +.multiline-text-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.multiline-text-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.multiline-text-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.multiline-text-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} 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 index 2511b63434..30768fa88f 100644 --- 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 @@ -1,11 +1,15 @@ -<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label multiline-text-widget"> +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label multiline-text-widget" + [class.multiline-text-widget__invalid]="!field.isValid"> <textarea class="mdl-textfield__input" type="text" rows= "3" [attr.id]="field.id" + [attr.required]="isRequired()" [(ngModel)]="field.value" (ngModelChange)="checkVisibility(field)" [disabled]="field.readOnly"> </textarea> <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> </div> + diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.spec.ts new file mode 100644 index 0000000000..54f18a3db2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.spec.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 { MultilineTextWidget } from './multiline-text.widget'; + +describe('MultilineTextWidget', () => { + + let widget: MultilineTextWidget; + + beforeEach(() => { + widget = new MultilineTextWidget(null); + }); + + it('should exist', () => { + expect(widget).toBeDefined(); + }); +}); 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 index d269746c80..3a36db6320 100644 --- 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 @@ -15,18 +15,19 @@ * limitations under the License. */ -import { Component } from '@angular/core'; -import { WidgetComponent } from './../widget.component'; - -declare let __moduleName: string; -declare var componentHandler; +import { Component, ElementRef } from '@angular/core'; +import { TextFieldWidgetComponent } from './../textfield-widget.component'; @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'multiline-text-widget', templateUrl: './multiline-text.widget.html', styleUrls: ['./multiline-text.widget.css'] }) -export class MultilineTextWidget extends WidgetComponent { +export class MultilineTextWidget extends TextFieldWidgetComponent { + + constructor(elementRef: ElementRef) { + super(elementRef); + } } 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 index eef6cf2a3f..798e231ed5 100644 --- 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 @@ -1,3 +1,20 @@ -:host .number-widget { +.number-widget { width: 100%; } + +.number-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} + 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 index 3a5bd8b474..3b0736f377 100644 --- 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 @@ -1,11 +1,13 @@ -<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label number-widget"> +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label number-widget" + [class.number-widget__invalid]="!field.isValid"> <input class="mdl-textfield__input" type="text" pattern="-?[0-9]*(\.[0-9]+)?" [attr.id]="field.id" + [attr.required]="isRequired()" [(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> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> </div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.spec.ts new file mode 100644 index 0000000000..c43e209c61 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.spec.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 { NumberWidget } from './number.widget'; + +describe('NumberWidget', () => { + + let widget: NumberWidget; + + beforeEach(() => { + widget = new NumberWidget(null); + }); + + it('should exist', () => { + expect(widget).toBeDefined(); + }); +}); 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 index cf4170ae1a..f3a04d58a4 100644 --- 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 @@ -15,18 +15,19 @@ * limitations under the License. */ -import { Component } from '@angular/core'; -import { WidgetComponent } from './../widget.component'; - -declare let __moduleName: string; -declare var componentHandler; +import { Component, ElementRef } from '@angular/core'; +import { TextFieldWidgetComponent } from './../textfield-widget.component'; @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'number-widget', templateUrl: './number.widget.html', styleUrls: ['./number.widget.css'] }) -export class NumberWidget extends WidgetComponent { +export class NumberWidget extends TextFieldWidgetComponent { + + constructor(elementRef: ElementRef) { + super(elementRef); + } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.css new file mode 100644 index 0000000000..2d444f1b7f --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.css @@ -0,0 +1,45 @@ +.people-widget { + width: 100%; +} + +.people-widget--autocomplete { + background-color: #fff; + position: absolute; + z-index: 5; + color: #555; + margin: -15px 0 0 0; +} + +.people-widget--autocomplete > ul { + list-style-type: none; + position: static; + + height: auto; + width: auto; + min-width: 124px; + padding: 8px 0; + margin: 0; + + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); + border-radius: 2px; +} + +.people-widget--autocomplete > ul > li { + opacity: 1; +} + +.people-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.people-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.people-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.people-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.html new file mode 100644 index 0000000000..a3896ab26e --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.html @@ -0,0 +1,22 @@ +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label people-widget" + [class.people-widget__invalid]="!field.isValid"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [(ngModel)]="value" + (ngModelChange)="checkVisibility(field)" + (keyup)="onKeyUp($event)" + (blur)="onBlur()" + [disabled]="field.readOnly"> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> +</div> +<div class="people-widget--autocomplete mdl-shadow--2dp" *ngIf="popupVisible && users.length > 0"> + <ul> + <li *ngFor="let item of users" + class="mdl-menu__item" + (click)="onItemClick(item, $event)"> + {{getDisplayName(item)}} + </li> + </ul> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts new file mode 100644 index 0000000000..dc6309c157 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts @@ -0,0 +1,272 @@ +/*! + * @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 { ElementRef } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { PeopleWidget } from './people.widget'; +import { FormService } from '../../../services/form.service'; +import { FormModel } from '../core/form.model'; +import { FormFieldModel } from '../core/form-field.model'; +import { GroupUserModel } from '../core/group-user.model'; + +describe('PeopleWidget', () => { + + let componentHandler; + let elementRef: ElementRef; + let formService: FormService; + let widget: PeopleWidget; + + beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + + formService = new FormService(null, null); + elementRef = new ElementRef(null); + widget = new PeopleWidget(formService, elementRef); + widget.field = new FormFieldModel(new FormModel()); + }); + + it('should return empty display name for missing model', () => { + expect(widget.getDisplayName(null)).toBe(''); + }); + + it('should return full name for a given model', () => { + let model = new GroupUserModel({ + firstName: 'John', + lastName: 'Doe' + }); + expect(widget.getDisplayName(model)).toBe('John Doe'); + }); + + it('should skip first name for display name', () => { + let model = new GroupUserModel({ firstName: null, lastName: 'Doe' }); + expect(widget.getDisplayName(model)).toBe('Doe'); + }); + + it('should skip last name for display name', () => { + let model = new GroupUserModel({ firstName: 'John', lastName: null }); + expect(widget.getDisplayName(model)).toBe('John'); + }); + + it('should flush value on blur', (done) => { + spyOn(widget, 'flushValue').and.stub(); + widget.onBlur(); + + setTimeout(() => { + expect(widget.flushValue).toHaveBeenCalled(); + done(); + }, 200); + }); + + it('should init value from the field', () => { + widget.field.value = new GroupUserModel({ + firstName: 'John', + lastName: 'Doe' + }); + + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); + + widget.ngOnInit(); + expect(widget.value).toBe('John Doe'); + }); + + it('should prevent default behaviour on option item click', () => { + let event = jasmine.createSpyObj('event', ['preventDefault']); + widget.onItemClick(null, event); + expect(event.preventDefault).toHaveBeenCalled(); + }); + + it('should update values on item click', () => { + let item = new GroupUserModel({ firstName: 'John', lastName: 'Doe' }); + + widget.onItemClick(item, null); + expect(widget.field.value).toBe(item); + expect(widget.value).toBe('John Doe'); + }); + + it('should require form field to setup values on init', () => { + widget.field = null; + widget.ngOnInit(); + + expect(widget.value).toBeUndefined(); + expect(widget.groupId).toBeUndefined(); + }); + + it('should setup group restriction', () => { + widget.ngOnInit(); + expect(widget.groupId).toBeUndefined(); + + widget.field.params = { restrictWithGroup: { id: '<id>' } }; + widget.ngOnInit(); + expect(widget.groupId).toBe('<id>'); + }); + + it('should fetch users by search term', () => { + let users = [{}, {}]; + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(users); + observer.complete(); + }) + ); + + widget.value = 'user1'; + widget.onKeyUp(null); + + expect(formService.getWorkflowUsers).toHaveBeenCalledWith(widget.value, widget.groupId); + expect(widget.users).toBe(users); + expect(widget.popupVisible).toBeTruthy(); + }); + + it('should fetch users by search term and group id', () => { + let users = [{}, {}]; + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(users); + observer.complete(); + }) + ); + + widget.value = 'user1'; + widget.groupId = '1001'; + widget.onKeyUp(null); + + expect(formService.getWorkflowUsers).toHaveBeenCalledWith(widget.value, widget.groupId); + expect(widget.users).toBe(users); + expect(widget.popupVisible).toBeTruthy(); + }); + + it('should fetch users and show no popup', () => { + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); + + widget.value = 'user1'; + widget.onKeyUp(null); + + expect(formService.getWorkflowUsers).toHaveBeenCalledWith(widget.value, widget.groupId); + expect(widget.users).toEqual([]); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should require search term to fetch users', () => { + spyOn(formService, 'getWorkflowUsers').and.stub(); + + widget.value = null; + widget.onKeyUp(null); + + expect(formService.getWorkflowUsers).not.toHaveBeenCalled(); + }); + + it('should not fetch users due to constraint violation', () => { + spyOn(formService, 'getWorkflowUsers').and.stub(); + + widget.value = '123'; + widget.minTermLength = 4; + widget.onKeyUp(null); + + expect(formService.getWorkflowUsers).not.toHaveBeenCalled(); + }); + + it('should hide popup on value flush', () => { + widget.popupVisible = true; + widget.flushValue(); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should update form on value flush', () => { + spyOn(widget.field, 'updateForm').and.callThrough(); + widget.flushValue(); + expect(widget.field.updateForm).toHaveBeenCalled(); + }); + + it('should flush value and update field', () => { + widget.users = [ + new GroupUserModel({ firstName: 'Tony', lastName: 'Stark' }), + new GroupUserModel({ firstName: 'John', lastName: 'Doe' }) + ]; + widget.value = 'John Doe'; + widget.flushValue(); + + expect(widget.value).toBe('John Doe'); + expect(widget.field.value).toBe(widget.users[1]); + }); + + it('should be case insensitive when flushing field', () => { + widget.users = [ + new GroupUserModel({ firstName: 'Tony', lastName: 'Stark' }), + new GroupUserModel({ firstName: 'John', lastName: 'Doe' }) + ]; + widget.value = 'TONY sTaRk'; + widget.flushValue(); + + expect(widget.value).toBe('Tony Stark'); + expect(widget.field.value).toBe(widget.users[0]); + }); + + it('should reset value and field on flush', () => { + widget.value = 'Missing User'; + widget.field.value = {}; + widget.flushValue(); + + expect(widget.value).toBeNull(); + expect(widget.field.value).toBeNull(); + }); + + it('should setup mdl textfield on view init', () => { + spyOn(widget, 'setupMaterialComponents').and.callThrough(); + spyOn(widget, 'setupMaterialTextField').and.callThrough(); + + widget.value = '<value>'; + widget.ngAfterViewInit(); + + expect(widget.setupMaterialComponents).toHaveBeenCalledWith(componentHandler); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should require component handler to setup textfield', () => { + expect(widget.setupMaterialComponents(null)).toBeFalsy(); + }); + + it('should require element reference to setup textfield', () => { + let w = new PeopleWidget(formService, null); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeFalsy(); + + w = new PeopleWidget(formService, elementRef); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeTruthy(); + }); + + it('should require value to setup textfield', () => { + widget.value = '<value>'; + expect(widget.setupMaterialComponents(componentHandler)).toBeTruthy(); + + widget.value = null; + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts new file mode 100644 index 0000000000..ec0bf04db7 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts @@ -0,0 +1,134 @@ +/*! + * @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, ElementRef } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; +import { FormService } from '../../../services/form.service'; +import { GroupModel } from '../core/group.model'; +import { GroupUserModel } from '../core/group-user.model'; + +@Component({ + moduleId: module.id, + selector: 'people-widget', + templateUrl: './people.widget.html', + styleUrls: ['./people.widget.css'] +}) +export class PeopleWidget extends WidgetComponent implements OnInit { + + popupVisible: boolean = false; + minTermLength: number = 1; + value: string; + users: GroupUserModel[] = []; + groupId: string; + + constructor(private formService: FormService, + private elementRef: ElementRef) { + super(); + } + + // TODO: investigate, called 2 times + // https://github.com/angular/angular/issues/6782 + ngOnInit() { + if (this.field) { + let user: GroupUserModel = this.field.value; + if (user) { + this.value = this.getDisplayName(user); + } + + let params = this.field.params; + if (params && params['restrictWithGroup']) { + let restrictWithGroup = <GroupModel> params['restrictWithGroup']; + this.groupId = restrictWithGroup.id; + } + + // Load auto-completion for previously saved value + if (this.value) { + this.formService + .getWorkflowUsers(this.value, this.groupId) + .subscribe((result: GroupUserModel[]) => this.users = result || []); + } + } + } + + onKeyUp(event: KeyboardEvent) { + if (this.value && this.value.length >= this.minTermLength) { + this.formService.getWorkflowUsers(this.value, this.groupId) + .subscribe((result: GroupUserModel[]) => { + this.users = result || []; + this.popupVisible = this.users.length > 0; + }); + } else { + this.popupVisible = false; + } + } + + onBlur() { + setTimeout(() => { + this.flushValue(); + }, 200); + } + + flushValue() { + this.popupVisible = false; + + let option = this.users.find(item => { + let fullName = this.getDisplayName(item).toLocaleLowerCase(); + return fullName === this.value.toLocaleLowerCase(); + }); + + if (option) { + this.field.value = option; + this.value = this.getDisplayName(option); + } else { + this.field.value = null; + this.value = null; + } + + this.field.updateForm(); + } + + getDisplayName(model: GroupUserModel) { + if (model) { + let displayName = `${model.firstName || ''} ${model.lastName || ''}`; + return displayName.trim(); + } + + return ''; + } + + // TODO: still causes onBlur execution + onItemClick(item: GroupUserModel, event: Event) { + if (item) { + this.field.value = item; + this.value = this.getDisplayName(item); + } + if (event) { + event.preventDefault(); + } + } + + setupMaterialComponents(handler: any): boolean { + super.setupMaterialComponents(handler); + if (handler) { + if (this.elementRef && this.value) { + this.setupMaterialTextField(this.elementRef, handler, this.value); + return true; + } + } + return false; + } +} 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 index 0fe4eb9fe7..f835d42bd9 100644 --- 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 @@ -1 +1,17 @@ .radio-buttons-widget {} + +.radio-buttons-widget__invalid .mdl-radio__label { + color: #d50000; +} + +.radio-buttons-widget__invalid .radio-buttons-widget__label { + color: #d50000; +} + +.radio-buttons-widget__invalid .radio-buttons-widget__label:after { + background-color: #d50000; +} + +.radio-buttons-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} 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 index a832a925f2..ea4bf5924f 100644 --- 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 @@ -1,15 +1,18 @@ -<div class="radio-buttons-widget"> +<div class="radio-buttons-widget" + [class.radio-buttons-widget__invalid]="!field.isValid" [id]="field.id" *ngIf="field?.isVisible"> + <label class="radio-buttons-widget__label" [attr.for]="field.id">{{field.name}}</label> <div *ngFor="let opt of field.options"> - <label [attr.for]="opt.id" class="mdl-radio mdl-js-radio"> + <label [id]="opt.id" [attr.for]="field.id + '-' + 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.id]="field.id + '-' + opt.id" [attr.name]="field.id" [attr.value]="opt.id" [disabled]="field.readOnly" - (click)="field.value = opt.id"> + (click)="onOptionClick(opt.id)"> <span class="mdl-radio__label">{{opt.name}}</span> </label> </div> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> </div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.spec.ts new file mode 100644 index 0000000000..2431b2af33 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/radio-buttons/radio-buttons.widget.spec.ts @@ -0,0 +1,205 @@ +/*! + * @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 { FormService } from '../../../services/form.service'; +import { RadioButtonsWidget } from './radio-buttons.widget'; +import { FormModel } from './../core/form.model'; +import { FormFieldModel } from './../core/form-field.model'; +import { CoreModule } from 'ng2-alfresco-core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { EcmModelService } from '../../../services/ecm-model.service'; + +describe('RadioButtonsWidget', () => { + + let formService: FormService; + let widget: RadioButtonsWidget; + + beforeEach(() => { + formService = new FormService(null, null); + widget = new RadioButtonsWidget(formService); + widget.field = new FormFieldModel(new FormModel(), {restUrl: '<url>'}); + }); + + it('should request field values from service', () => { + const taskId = '<form-id>'; + const fieldId = '<field-id>'; + + let form = new FormModel({ + taskId: taskId + }); + + widget.field = new FormFieldModel(form, { + id: fieldId, + restUrl: '<url>' + }); + + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next(null); + observer.complete(); + })); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId); + }); + + it('should update form on values fetched', () => { + let form = widget.field; + spyOn(form, 'updateForm').and.stub(); + + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next(null); + observer.complete(); + })); + widget.ngOnInit(); + expect(form.updateForm).toHaveBeenCalled(); + }); + + it('should require field with rest URL to fetch data', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next(null); + observer.complete(); + })); + + let field = widget.field; + widget.field = null; + widget.ngOnInit(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); + widget.field = field; + + widget.field.restUrl = null; + widget.ngOnInit(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); + + widget.field.restUrl = '<url>'; + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + }); + + it('should log error to console by default', () => { + spyOn(console, 'error').and.stub(); + widget.handleError('Err'); + expect(console.error).toHaveBeenCalledWith('Err'); + }); + + it('should update the field value when an option is selected', () => { + spyOn(widget, 'checkVisibility').and.stub(); + widget.onOptionClick('fake-opt'); + + expect(widget.field.value).toEqual('fake-opt'); + }); + + it('should emit field change event when option is clicked', (done) => { + widget.fieldChanged.subscribe((field) => { + expect(field.value).toEqual('fake-opt'); + done(); + }); + widget.onOptionClick('fake-opt'); + }); + + describe('when template is ready', () => { + let radioButtonWidget: RadioButtonsWidget; + let fixture: ComponentFixture<RadioButtonsWidget>; + let element: HTMLElement; + let componentHandler; + + beforeEach(async(() => { + componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']); + window['componentHandler'] = componentHandler; + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [RadioButtonsWidget], + providers: [FormService, EcmModelService] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(RadioButtonsWidget); + radioButtonWidget = fixture.componentInstance; + element = fixture.nativeElement; + }); + })); + + beforeEach(() => { + radioButtonWidget.field = new FormFieldModel(new FormModel(), { + id: 'radio-id', + name: 'radio-name', + type: 'radio-buttons' + }); + radioButtonWidget.field.options = [{id: 'opt-1', name: 'opt-name-1'}, {id: 'opt-2', name: 'opt-name-2'}]; + radioButtonWidget.field.isVisible = true; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + it('should show visible radio buttons', async(() => { + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#radio-id')).toBeDefined(); + expect(element.querySelector('#opt-1')).toBeDefined(); + expect(element.querySelector('#radio-id-opt-1')).toBeDefined(); + expect(element.querySelector('#opt-2')).toBeDefined(); + expect(element.querySelector('#radio-id-opt-2')).toBeDefined(); + }); + })); + + it('should not show invisible radio buttons', async(() => { + radioButtonWidget.field.isVisible = false; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#radio-id')).toBeNull(); + expect(element.querySelector('#opt-1')).toBeNull(); + expect(element.querySelector('#opt-2')).toBeNull(); + }); + })); + + it('should hide radio button when it becomes not visible', async(() => { + radioButtonWidget.fieldChanged.subscribe((res) => { + radioButtonWidget.field.isVisible = false; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#radio-id')).toBeNull(); + expect(element.querySelector('#opt-1')).toBeNull(); + expect(element.querySelector('#opt-2')).toBeNull(); + }); + }); + radioButtonWidget.checkVisibility(null); + })); + + it('should show radio button when it becomes visible', async(() => { + radioButtonWidget.field.isVisible = false; + fixture.detectChanges(); + radioButtonWidget.fieldChanged.subscribe((res) => { + radioButtonWidget.field.isVisible = true; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#radio-id')).toBeDefined(); + expect(element.querySelector('#opt-1')).toBeDefined(); + expect(element.querySelector('#radio-id-opt-1')).toBeDefined(); + expect(element.querySelector('#opt-2')).toBeDefined(); + expect(element.querySelector('#radio-id-opt-2')).toBeDefined(); + }); + }); + radioButtonWidget.checkVisibility(null); + })); + }); + +}); 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 index e528813c72..a3286c2066 100644 --- 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 @@ -15,18 +15,70 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { WidgetComponent } from './../widget.component'; - -declare let __moduleName: string; -declare var componentHandler; +import { FormService } from '../../../services/form.service'; +import { FormFieldOption } from './../core/form-field-option'; @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'radio-buttons-widget', templateUrl: './radio-buttons.widget.html', styleUrls: ['./radio-buttons.widget.css'] }) -export class RadioButtonsWidget extends WidgetComponent { +export class RadioButtonsWidget extends WidgetComponent implements OnInit { + + constructor(private formService: FormService) { + super(); + } + + ngOnInit() { + if (this.field && this.field.restUrl) { + if (this.field.form.processDefinitionId) { + this.getOptionsByProcessDefinitionId(); + } else { + this.getOptionsByTaskId(); + } + } + } + + getOptionsByTaskId() { + this.formService + .getRestFieldValues( + this.field.form.taskId, + this.field.id + ) + .subscribe( + (result: FormFieldOption[]) => { + this.field.options = result || []; + this.field.updateForm(); + }, + this.handleError + ); + } + + getOptionsByProcessDefinitionId() { + this.formService + .getRestFieldValuesByProcessId( + this.field.form.processDefinitionId, + this.field.id + ) + .subscribe( + (result: FormFieldOption[]) => { + this.field.options = result || []; + this.field.updateForm(); + }, + this.handleError + ); + } + + onOptionClick(optionSelected: any) { + this.field.value = optionSelected; + this.checkVisibility(this.field); + } + + handleError(error: any) { + console.error(error); + } } 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 index 4c2f2cd5cb..f4dd53c292 100644 --- 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 @@ -1,20 +1,36 @@ <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" + <a *ngFor="let tab of visibleTabs; let isFirst = first" + id="title-{{tab.id}}" [href]="'#' + tab.id" class="mdl-tabs__tab" [class.is-active]="isFirst"> {{tab.title}} </a> </div> - <div *ngFor="let tab of tabs; let isFirst = first" + <div *ngFor="let tab of visibleTabs; 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 *ngFor="let field of tab.fields"> + <div [ngSwitch]="field.type"> + <div *ngSwitchCase="'container'"> + <container-widget [content]="field" (formValueChanged)="tabChanged($event);"></container-widget> + </div> + <div *ngSwitchCase="'group'"> + <container-widget [content]="field" (formValueChanged)="tabChanged($event);"></container-widget> + </div> + <div *ngSwitchCase="'dynamic-table'"> + <dynamic-table-widget [content]="field"></dynamic-table-widget> + </div> + <div *ngSwitchCase="'readonly'"> + <display-value-widget [field]="field.field" (fieldChanged)="tabChanged($event);"></display-value-widget> + </div> + <div *ngSwitchDefault> + <span>UNKNOWN WIDGET TYPE: {{field.type}}</span> + </div> + </div> + </div> </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 index 2ffc23f974..c61bf2c9ce 100644 --- 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 @@ -15,9 +15,14 @@ * limitations under the License. */ -import { it, describe, expect, beforeEach } from '@angular/core/testing'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { FormModel } from '../core/form.model'; +import { FormFieldModel } from '../core/form-field.model'; +import { fakeFormJson } from '../../../services/assets/widget-visibility.service.mock'; import { TabsWidget } from './tabs.widget'; -import { TabModel } from './../core/tab.model'; +import { TabModel } from '../core/tab.model'; +import { WIDGET_DIRECTIVES } from '../index'; +import { CoreModule } from 'ng2-alfresco-core'; describe('TabsWidget', () => { @@ -57,4 +62,116 @@ describe('TabsWidget', () => { expect(widget.setupMaterialComponents()).toBeFalsy(); }); + it('should emit tab changed event', (done) => { + let field = new FormFieldModel(null); + widget.formTabChanged.subscribe(tab => { + expect(tab).toBe(field); + done(); + }); + widget.tabChanged(field); + }); + + it('should remove invisible tabs', () => { + let fakeTab = new TabModel(null, {id: 'fake-tab-id', title: 'fake-tab-title'}); + fakeTab.isVisible = false; + widget.tabs.push(fakeTab); + widget.ngAfterContentChecked(); + + expect(widget.visibleTabs.length).toBe(0); + }); + + it('should leave visible tabs', () => { + let fakeTab = new TabModel(null, {id: 'fake-tab-id', title: 'fake-tab-title'}); + fakeTab.isVisible = true; + widget.tabs.push(fakeTab); + widget.ngAfterContentChecked(); + + expect(widget.visibleTabs.length).toBe(1); + expect(widget.visibleTabs[0].id).toBe('fake-tab-id'); + expect(widget.visibleTabs[0].title).toBe('fake-tab-title'); + expect(widget.visibleTabs[0].isVisible).toBeTruthy(); + }); + + describe('when template is ready', () => { + let tabWidgetComponent: TabsWidget; + let fixture: ComponentFixture<TabsWidget>; + let element: HTMLElement; + let fakeTabVisible: TabModel; + let fakeTabInvisible: TabModel; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [WIDGET_DIRECTIVES] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(TabsWidget); + tabWidgetComponent = fixture.componentInstance; + element = fixture.nativeElement; + }); + })); + + beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', + ['upgradeAllRegistered', 'upgradeElement', 'downgradeElements']); + window['componentHandler'] = componentHandler; + fakeTabVisible = new TabModel(new FormModel(fakeFormJson), { + id: 'tab-id-visible', + title: 'tab-title-visible' + }); + fakeTabVisible.isVisible = true; + fakeTabInvisible = new TabModel(new FormModel(fakeFormJson), { + id: 'tab-id-invisible', + title: 'tab-title-invisible' + }); + fakeTabInvisible.isVisible = false; + tabWidgetComponent.tabs.push(fakeTabVisible); + tabWidgetComponent.tabs.push(fakeTabInvisible); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + it('should show only visible tabs', () => { + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#tab-id-visible')).toBeDefined(); + expect(element.querySelector('#tab-id-visible')).not.toBeNull(); + expect(element.querySelector('#tab-id-invisible')).toBeNull(); + expect(element.querySelector('#title-tab-id-visible')).toBeDefined(); + expect(element.querySelector('#title-tab-id-visible').innerHTML).toContain('tab-title-visible'); + }); + }); + + it('should show tab when it became visible', async(() => { + fixture.detectChanges(); + tabWidgetComponent.formTabChanged.subscribe((res) => { + tabWidgetComponent.tabs[1].isVisible = true; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#tab-id-invisible')).not.toBeNull(); + expect(element.querySelector('#title-tab-id-invisible').innerHTML).toContain('tab-title-invisible'); + }); + }); + tabWidgetComponent.tabChanged(null); + })); + + it('should hide tab when it became not visible', async(() => { + fixture.detectChanges(); + tabWidgetComponent.formTabChanged.subscribe((res) => { + tabWidgetComponent.tabs[0].isVisible = false; + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#tab-id-visible')).toBeNull(); + expect(element.querySelector('#title-tab-id-visible')).toBeNull(); + }); + }); + tabWidgetComponent.tabChanged(null); + })); + + }); }); 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 index 8c4d9de949..9110beb9ef 100644 --- 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 @@ -15,33 +15,42 @@ * 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; +import { Component, Input, AfterViewInit, AfterContentChecked, EventEmitter, Output } from '@angular/core'; +import { TabModel, FormFieldModel } from './../core/index'; @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'tabs-widget', - templateUrl: './tabs.widget.html', - directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget] + templateUrl: './tabs.widget.html' }) -export class TabsWidget implements AfterViewInit { +export class TabsWidget implements AfterContentChecked, AfterViewInit { @Input() tabs: TabModel[] = []; + @Output() + formTabChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>(); + + visibleTabs: TabModel[] = []; + hasTabs() { return this.tabs && this.tabs.length > 0; } + ngAfterContentChecked() { + this.filterVisibleTabs(); + } + ngAfterViewInit() { this.setupMaterialComponents(); } + filterVisibleTabs() { + this.visibleTabs = this.tabs.filter(tab => { + return tab.isVisible; + }); + } + setupMaterialComponents(): boolean { // workaround for MDL issues with dynamic components if (componentHandler) { @@ -50,4 +59,9 @@ export class TabsWidget implements AfterViewInit { } return false; } + + tabChanged(field: FormFieldModel) { + this.formTabChanged.emit(field); + } + } 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 index 8fe1b900e1..1c70629adf 100644 --- 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 @@ -1,3 +1,20 @@ -:host .text-widget { +.text-widget { width: 100%; } + + +.text-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.text-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.text-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.text-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} 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 index d74aac765a..95a46c3c44 100644 --- 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 @@ -1,9 +1,12 @@ -<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label text-widget"> +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label text-widget" + [class.text-widget__invalid]="!field.isValid"> <input class="mdl-textfield__input" type="text" [attr.id]="field.id" + [attr.required]="isRequired()" [(ngModel)]="field.value" (ngModelChange)="checkVisibility(field)" [disabled]="field.readOnly"> <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> </div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.spec.ts new file mode 100644 index 0000000000..14548449fe --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.spec.ts @@ -0,0 +1,68 @@ +/*! + * @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 { ElementRef } from '@angular/core'; +import { TextWidget } from './text.widget'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; + +describe('TextWidget', () => { + + let widget: TextWidget; + let elementRef: ElementRef; + let componentHandler; + + beforeEach(() => { + elementRef = new ElementRef(null); + widget = new TextWidget(elementRef); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + }); + + it('should upgrade material textfield', () => { + spyOn(widget, 'setupMaterialTextField').and.stub(); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.TEXT, + value: '<text>' + }); + widget.ngAfterViewInit(); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should require mdl component handler to setup textfield', () => { + expect(widget.setupMaterialComponents(null)).toBeFalsy(); + }); + + it('should require element reference to setup textfield', () => { + widget = new TextWidget(null); + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); + + it('should require field value to setup textfield', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.TEXT, + value: null + }); + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); + +}); 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 index 44fc7c8494..e8498ab545 100644 --- 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 @@ -15,18 +15,19 @@ * limitations under the License. */ -import { Component } from '@angular/core'; -import { WidgetComponent } from './../widget.component'; - -declare let __moduleName: string; -declare var componentHandler; +import { Component, ElementRef } from '@angular/core'; +import { TextFieldWidgetComponent } from './../textfield-widget.component'; @Component({ - moduleId: __moduleName, + moduleId: module.id, selector: 'text-widget', templateUrl: './text.widget.html', styleUrls: ['./text.widget.css'] }) -export class TextWidget extends WidgetComponent { +export class TextWidget extends TextFieldWidgetComponent { + + constructor(elementRef: ElementRef) { + super(elementRef); + } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/textfield-widget.component.ts b/ng2-components/ng2-activiti-form/src/components/widgets/textfield-widget.component.ts new file mode 100644 index 0000000000..5037574e62 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/textfield-widget.component.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 { ElementRef } from '@angular/core'; +import { WidgetComponent } from './widget.component'; + +export abstract class TextFieldWidgetComponent extends WidgetComponent { + + protected elementRef: ElementRef; + + constructor(elementRef: ElementRef) { + super(); + this.elementRef = elementRef; + } + + setupMaterialComponents(handler: any): boolean { + super.setupMaterialComponents(handler); + // workaround for MDL issues with dynamic components + if (handler) { + if (this.elementRef && this.hasValue()) { + return this.setupMaterialTextField(this.elementRef, handler, this.field.value.toString()); + } + } + return false; + } + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.css new file mode 100644 index 0000000000..31b3776aae --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.css @@ -0,0 +1,47 @@ +.typeahead-widget { + width: 100%; +} + +.typeahead-autocomplete { + background-color: #fff; + position: absolute; + z-index: 5; + color: #555; + margin: -15px 0 0 0; +} + +.typeahead-autocomplete > ul { + list-style-type: none; + position: static; + + height: auto; + width: auto; + min-width: 124px; + padding: 8px 0; + margin: 0; + + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); + border-radius: 2px; +} + +.typeahead-autocomplete > ul > li { + opacity: 1; +} + + + +.typeahead-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.typeahead-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.typeahead-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.typeahead-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.html new file mode 100644 index 0000000000..77dfc9e853 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.html @@ -0,0 +1,22 @@ +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label typeahead-widget" + [class.is-dirty]="value" + [class.typeahead-widget__invalid]="!field.isValid" id="typehead-div"> + <input class="mdl-textfield__input" + type="text" + [attr.id]="field.id" + [(ngModel)]="value" + (keyup)="onKeyUp($event)" + (blur)="onBlur()" + [disabled]="field.readOnly"> + <label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> +</div> +<div class="typeahead-autocomplete mdl-shadow--2dp" *ngIf="options.length > 0 && popupVisible"> + <ul> + <li *ngFor="let item of options" + class="mdl-menu__item" + (click)="onItemClick(item, $event)"> + {{item.name}} + </li> + </ul> +</div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.spec.ts new file mode 100644 index 0000000000..03e0b906d2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.spec.ts @@ -0,0 +1,340 @@ +/*! + * @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 { TypeaheadWidget } from './typeahead.widget'; +import { FormService } from '../../../services/form.service'; +import { FormModel } from '../core/form.model'; +import { FormFieldModel } from '../core/form-field.model'; +import { FormFieldOption } from '../core/form-field-option'; + +describe('TypeaheadWidget', () => { + + let formService: FormService; + let widget: TypeaheadWidget; + + beforeEach(() => { + formService = new FormService(null, null); + widget = new TypeaheadWidget(formService); + widget.field = new FormFieldModel(new FormModel()); + }); + + it('should request field values from service', () => { + const taskId = '<form-id>'; + const fieldId = '<field-id>'; + + let form = new FormModel({ + taskId: taskId + }); + + widget.field = new FormFieldModel(form, { + id: fieldId + }); + + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next(null); + observer.complete(); + })); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId); + }); + + it('should handle error when requesting fields', () => { + const err = 'Error'; + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.throw(err)); + spyOn(widget, 'handleError').and.callThrough(); + + widget.ngOnInit(); + + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.handleError).toHaveBeenCalledWith(err); + }); + + it('should log error to console by default', () => { + spyOn(console, 'error').and.stub(); + widget.handleError('Err'); + expect(console.error).toHaveBeenCalledWith('Err'); + }); + + it('should show popup on key up', () => { + + spyOn(widget, 'getOptions').and.returnValue([{}, {}]); + + widget.minTermLength = 1; + widget.value = 'some value'; + + widget.popupVisible = false; + widget.onKeyUp(null); + expect(widget.popupVisible).toBeTruthy(); + }); + + it('should require items to show popup', () => { + widget.minTermLength = 1; + widget.value = 'some value'; + + widget.popupVisible = false; + widget.onKeyUp(null); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should require value to show popup', () => { + widget.minTermLength = 1; + widget.value = ''; + + widget.popupVisible = false; + widget.onKeyUp(null); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should require value to be of min length to show popup', () => { + spyOn(widget, 'getOptions').and.returnValue([{}, {}]); + + widget.minTermLength = 3; + widget.value = 'v'; + + // value less than constraint + widget.popupVisible = false; + widget.onKeyUp(null); + expect(widget.popupVisible).toBeFalsy(); + + // value satisfies constraint + widget.value = 'value'; + widget.onKeyUp(null); + expect(widget.popupVisible).toBeTruthy(); + + // value gets less than allowed again + widget.value = 'va'; + widget.onKeyUp(null); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should flush value on blur', (done) => { + spyOn(widget, 'flushValue').and.stub(); + widget.onBlur(); + + setTimeout(() => { + expect(widget.flushValue).toHaveBeenCalled(); + done(); + }, 200); + }); + + it('should prevent default behaviour on option item click', () => { + let event = jasmine.createSpyObj('event', ['preventDefault']); + widget.onItemClick(null, event); + expect(event.preventDefault).toHaveBeenCalled(); + }); + + it('should update values on option item click', () => { + let option: FormFieldOption = <FormFieldOption> { + id: '1', + name: 'name' + }; + + widget.onItemClick(option, null); + expect(widget.field.value).toBe(option.id); + expect(widget.value).toBe(option.name); + }); + + it('should setup initial value', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next([ + { id: '1', name: 'One' }, + { id: '2', name: 'Two' } + ]); + observer.complete(); + })); + + widget.field.value = '2'; + widget.ngOnInit(); + + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBe('Two'); + }); + + it('should not setup initial value due to missing option', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next([ + { id: '1', name: 'One' }, + { id: '2', name: 'Two' } + ]); + observer.complete(); + })); + + widget.field.value = '3'; + widget.ngOnInit(); + + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBeUndefined(); + }); + + it('should setup field options on load', () => { + let options: FormFieldOption[] = [ + { id: '1', name: 'One' }, + { id: '2', name: 'Two' } + ]; + + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next(options); + observer.complete(); + })); + + widget.ngOnInit(); + expect(widget.field.options).toEqual(options); + }); + + it('should update form upon options setup', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { + observer.next([]); + observer.complete(); + })); + + spyOn(widget.field, 'updateForm').and.callThrough(); + widget.ngOnInit(); + expect(widget.field.updateForm).toHaveBeenCalled(); + }); + + it('should get filtered options', () => { + let options: FormFieldOption[] = [ + { id: '1', name: 'Item one' }, + { id: '2', name: 'Item two' } + ]; + widget.field.options = options; + widget.value = 'tw'; + + let filtered = widget.getOptions(); + expect(filtered.length).toBe(1); + expect(filtered[0]).toEqual(options[1]); + }); + + it('should be case insensitive when filtering options', () => { + let options: FormFieldOption[] = [ + { id: '1', name: 'Item one' }, + { id: '2', name: 'iTEM TWo' } + ]; + widget.field.options = options; + widget.value = 'tW'; + + let filtered = widget.getOptions(); + expect(filtered.length).toBe(1); + expect(filtered[0]).toEqual(options[1]); + }); + + it('should hide popup on flush', () => { + widget.popupVisible = true; + widget.flushValue(); + expect(widget.popupVisible).toBeFalsy(); + }); + + it('should update form on value flush', () => { + spyOn(widget.field, 'updateForm').and.callThrough(); + widget.flushValue(); + expect(widget.field.updateForm).toHaveBeenCalled(); + }); + + it('should flush selected value', () => { + let options: FormFieldOption[] = [ + { id: '1', name: 'Item one' }, + { id: '2', name: 'Item Two' } + ]; + + widget.field.options = options; + widget.value = 'Item Two'; + widget.flushValue(); + + expect(widget.value).toBe(options[1].name); + expect(widget.field.value).toBe(options[1].id); + }); + + it('should be case insensitive when flushing value', () => { + let options: FormFieldOption[] = [ + { id: '1', name: 'Item one' }, + { id: '2', name: 'iTEM TWo' } + ]; + + widget.field.options = options; + widget.value = 'ITEM TWO'; + widget.flushValue(); + + expect(widget.value).toBe(options[1].name); + expect(widget.field.value).toBe(options[1].id); + }); + + it('should reset fields when flushing missing option value', () => { + widget.field.options = [ + { id: '1', name: 'Item one' }, + { id: '2', name: 'Item two' } + ]; + widget.value = 'Missing item'; + widget.flushValue(); + + expect(widget.value).toBeNull(); + expect(widget.field.value).toBeNull(); + }); + + it('should reset fields when flushing incorrect value', () => { + widget.field.options = [ + { id: '1', name: 'Item one' }, + { id: '2', name: 'Item two' } + ]; + widget.field.value = 'Item two'; + widget.value = 'Item two!'; + widget.flushValue(); + + expect(widget.value).toBeNull(); + expect(widget.field.value).toBeNull(); + }); + + it('should reset fields when flushing value having no options', () => { + widget.field.options = null; + widget.field.value = 'item 1'; + widget.value = 'new item'; + widget.flushValue(); + + expect(widget.value).toBeNull(); + expect(widget.field.value).toBeNull(); + }); + + it('should emit field change event on item click', () => { + let event = jasmine.createSpyObj('event', ['preventDefault']); + let fakeField = new FormFieldModel(new FormModel(), {id: 'fakeField', value: 'fakeValue'}); + widget.field = fakeField; + let item = {id: 'fake-id-opt', name: 'fake-name-opt'}; + widget.onItemClick(item, event); + + widget.fieldChanged.subscribe((field) => { + expect(field).toBeDefined(); + expect(field.id).toEqual('fakeField'); + expect(field.value).toEqual('fake-id-opt'); + }); + }); + + it('should emit field change event on blur', (done) => { + spyOn(widget, 'flushValue').and.stub(); + let fakeField = new FormFieldModel(new FormModel(), {id: 'fakeField', value: 'fakeValue'}); + widget.field = fakeField; + widget.onBlur(); + + setTimeout(() => { + widget.fieldChanged.subscribe((field) => { + expect(field).toBeDefined(); + expect(field.id).toEqual('field-id'); + expect(field.value).toEqual('field-value'); + }); + done(); + }, 200); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.ts new file mode 100644 index 0000000000..bf336f180e --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.ts @@ -0,0 +1,155 @@ +/*! + * @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 { FormService } from './../../../services/form.service'; +import { WidgetComponent } from './../widget.component'; +import { FormFieldOption } from './../core/form-field-option'; + +@Component({ + moduleId: module.id, + selector: 'typeahead-widget', + templateUrl: './typeahead.widget.html', + styleUrls: ['./typeahead.widget.css'] +}) +export class TypeaheadWidget extends WidgetComponent implements OnInit { + + popupVisible: boolean = false; + minTermLength: number = 1; + value: string; + options: FormFieldOption[] = []; + + constructor(private formService: FormService) { + super(); + } + + ngOnInit() { + if (this.field.form.processDefinitionId) { + this.getValuesByProcessDefinitionId(); + } else { + this.getValuesByTaskId(); + } + } + + getValuesByTaskId() { + this.formService + .getRestFieldValues( + this.field.form.taskId, + this.field.id + ) + .subscribe( + (result: FormFieldOption[]) => { + let options = result || []; + this.field.options = options; + this.field.updateForm(); + + let fieldValue = this.field.value; + if (fieldValue) { + let toSelect = options.find(item => item.id === fieldValue); + if (toSelect) { + this.value = toSelect.name; + } + } + }, + this.handleError + ); + } + + getValuesByProcessDefinitionId() { + this.formService + .getRestFieldValuesByProcessId( + this.field.form.processDefinitionId, + this.field.id + ) + .subscribe( + (result: FormFieldOption[]) => { + let options = result || []; + this.field.options = options; + this.field.updateForm(); + + let fieldValue = this.field.value; + if (fieldValue) { + let toSelect = options.find(item => item.id === fieldValue); + if (toSelect) { + this.value = toSelect.name; + } + } + }, + this.handleError + ); + } + + getOptions(): FormFieldOption[] { + let val = this.value.toLocaleLowerCase(); + return this.field.options.filter(item => { + let name = item.name.toLocaleLowerCase(); + return name.indexOf(val) > -1; + }); + } + + onKeyUp(event: KeyboardEvent) { + if (this.value && this.value.length >= this.minTermLength) { + this.options = this.getOptions(); + this.popupVisible = this.options.length > 0; + } else { + this.popupVisible = false; + } + } + + onBlur() { + setTimeout(() => { + this.flushValue(); + this.checkVisibility(this.field); + }, 200); + } + + flushValue() { + this.popupVisible = false; + + let options = this.field.options || []; + let lValue = this.value ? this.value.toLocaleLowerCase() : null; + + let field = options.find(item => item.name && item.name.toLocaleLowerCase() === lValue); + if (field) { + this.field.value = field.id; + this.value = field.name; + } else { + this.field.value = null; + this.value = null; + } + + // TODO: seems to be not needed as field.value setter calls it + this.field.updateForm(); + } + + // TODO: still causes onBlur execution + onItemClick(item: FormFieldOption, event: Event) { + if (item) { + this.field.value = item.id; + this.value = item.name; + this.checkVisibility(this.field); + } + if (event) { + event.preventDefault(); + } + } + + handleError(error: any) { + console.error(error); + } + +} 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 index 1735deb447..4a628aad07 100644 --- 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 @@ -1,5 +1,6 @@ .upload-widget { - width:100% + width:100%; + word-break: break-all; } .upload-widget__icon { @@ -13,5 +14,21 @@ .upload-widget__reset { float: left; - margin-top: 4px; + margin-top: -2px; +} + +.upload-widget__invalid .upload-widget__label { + color: #d50000; +} + +.upload-widget__invalid .upload-widget__label:after { + background-color: #d50000; +} + +.upload-widget__invalid .upload-widget__file { + color: #d50000; +} + +.upload-widget__invalid .mdl-textfield__error { + visibility: visible !important; } 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 index f5fdaabb25..855f76a252 100644 --- 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 @@ -1,15 +1,19 @@ -<div class="upload-widget"> - - <label [attr.for]="field.id">{{field.name}}</label> +<div class="upload-widget" + [class.upload-widget__invalid]="!field.isValid"> + <label 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> + <i *ngIf="hasFile" class="material-icons upload-widget__icon">attachment</i> + <span *ngIf="hasFile" class="upload-widget__file">{{displayText}}</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> + <button *ngIf="hasFile" (click)="reset(file);" + class="mdl-button mdl-js-button mdl-button--icon upload-widget__reset"> + <i class="material-icons">highlight_off</i> + </button> </div> + <span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span> </div> diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.spec.ts new file mode 100644 index 0000000000..c53aee46d5 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.spec.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 { UploadWidget } from './upload.widget'; +import { AlfrescoSettingsService, AlfrescoAuthenticationService, AlfrescoApiService } from 'ng2-alfresco-core'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; + +describe('UploadWidget', () => { + + let widget: UploadWidget; + let settingsService: AlfrescoSettingsService; + let authService: AlfrescoAuthenticationService; + + beforeEach(() => { + settingsService = new AlfrescoSettingsService(); + authService = new AlfrescoAuthenticationService(settingsService, new AlfrescoApiService()); + widget = new UploadWidget(settingsService, authService); + }); + + it('should setup with field data', () => { + const fileName = 'hello world'; + const encodedFileName = encodeURI(fileName); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [ + { name: encodedFileName } + ] + }); + + widget.ngOnInit(); + expect(widget.hasFile).toBeTruthy(); + expect(widget.fileName).toBe(encodeURI(fileName)); + expect(widget.displayText).toBe(fileName); + }); + + it('should require form field to setup', () => { + widget.field = null; + widget.ngOnInit(); + + expect(widget.hasFile).toBeFalsy(); + expect(widget.fileName).toBeUndefined(); + expect(widget.displayText).toBeUndefined(); + }); + + it('should reset local properties', () => { + widget.hasFile = true; + widget.fileName = '<fileName>'; + widget.displayText = '<displayText>'; + + widget.reset(); + expect(widget.hasFile).toBeFalsy(); + expect(widget.fileName).toBeNull(); + expect(widget.displayText).toBeNull(); + }); + + it('should reset field value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [ + { name: 'filename' } + ] + }); + widget.reset(); + expect(widget.field.value).toBeNull(); + expect(widget.field.json.value).toBeNull(); + }); + +}); 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 index 2edf60dd6c..2dc1868e5b 100644 --- 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 @@ -19,63 +19,57 @@ 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, + moduleId: module.id, selector: 'upload-widget', templateUrl: './upload.widget.html', styleUrls: ['./upload.widget.css'] }) export class UploadWidget extends WidgetComponent implements OnInit { + hasFile: boolean; + fileName: string; + displayText: string; + 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; + this.fileName = file.name; + this.displayText = decodeURI(file.name); } - - return result; } reset() { - this.field.value = null; - this.field.json.value = null; this.hasFile = false; + this.fileName = null; + this.displayText = null; + + if (this.field) { + this.field.value = null; + this.field.json.value = null; + } } onFileChanged(event: any) { - let files = event.srcElement.files; + let files = event.target.files; if (files && files.length > 0) { let file = files[0]; this.hasFile = true; - this.fileName = file.name; + this.fileName = encodeURI(file.name); + this.displayText = file.name; let formData: FormData = new FormData(); - formData.append('file', file, file.name); + formData.append('file', file, this.fileName); let xhr: XMLHttpRequest = new XMLHttpRequest(); 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 index 849e84a255..0b2046438d 100644 --- 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 @@ -15,7 +15,7 @@ * limitations under the License. */ -import { it, describe, expect, beforeEach } from '@angular/core/testing'; +import { ElementRef } from '@angular/core'; import { WidgetComponent } from './widget.component'; import { FormFieldModel } from './core/form-field.model'; import { FormModel } from './core/form.model'; @@ -40,9 +40,7 @@ describe('WidgetComponent', () => { it('should setup MDL content only if component handler available', () => { let component = new WidgetComponent(); - expect(component.setupMaterialComponents()).toBeTruthy(); - - window['componentHandler'] = null; + expect(component.setupMaterialComponents(componentHandler)).toBeTruthy(); expect(component.setupMaterialComponents()).toBeFalsy(); }); @@ -50,7 +48,7 @@ describe('WidgetComponent', () => { let component = new WidgetComponent(); expect(component.hasField()).toBeFalsy(); - component.field = new FormFieldModel(null); + component.field = new FormFieldModel(new FormModel()); expect(component.hasField()).toBeTruthy(); }); @@ -83,4 +81,53 @@ describe('WidgetComponent', () => { component.checkVisibility(fakeField); }); + + it('should eval isRequired state of the field', () => { + let widget = new WidgetComponent(); + expect(widget.isRequired()).toBeFalsy(); + + widget.field = new FormFieldModel(null); + expect(widget.isRequired()).toBeFalsy(); + + widget.field = new FormFieldModel(null, { required: false }); + expect(widget.isRequired()).toBeFalsy(); + + widget.field = new FormFieldModel(null, { required: true }); + expect(widget.isRequired()).toBeTruthy(); + }); + + it('should require element reference to setup textfield', () => { + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(null, {}, 'value')).toBeFalsy(); + }); + + it('should require component handler to setup textfield', () => { + let elementRef = new ElementRef(null); + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(elementRef, null, 'value')).toBeFalsy(); + }); + + it('should require field value to setup textfield', () => { + let elementRef = new ElementRef(null); + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(elementRef, {}, null)).toBeFalsy(); + }); + + it('should setup textfield', () => { + let changeCalled = false; + let elementRef = new ElementRef({ + querySelector: function () { + return { + MaterialTextfield: { + change: function() { + changeCalled = true; + } + } + }; + } + }); + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(elementRef, {}, 'value')).toBeTruthy(); + expect(changeCalled).toBeTruthy(); + }); }); 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 index b27efe1214..b3d6f4e54d 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts @@ -15,17 +15,17 @@ * limitations under the License. */ -import { Input, AfterViewInit, Output, EventEmitter } from '@angular/core'; +import { Input, AfterViewInit, Output, EventEmitter, ElementRef } from '@angular/core'; import { FormFieldModel } from './core/index'; -declare let __moduleName: string; -declare var componentHandler; - /** * Base widget component. */ export class WidgetComponent implements AfterViewInit { + static DEFAULT_HYPERLINK_URL: string = '#'; + static DEFAULT_HYPERLINK_SCHEME: string = 'http://'; + @Input() field: FormFieldModel; @@ -36,22 +36,75 @@ export class WidgetComponent implements AfterViewInit { return this.field ? true : false; } + // Note for developers: + // returns <any> object to be able binding it to the <element reguired="required"> attribute + isRequired(): any { + if (this.field && this.field.required) { + return true; + } + return null; + } + + hasValue(): boolean { + return this.field && + this.field.value !== null && + this.field.value !== undefined; + } + ngAfterViewInit() { - this.setupMaterialComponents(); + this.setupMaterialComponents(componentHandler); this.fieldChanged.emit(this.field); } - setupMaterialComponents(): boolean { + setupMaterialComponents(handler?: any): boolean { // workaround for MDL issues with dynamic components - if (componentHandler) { - componentHandler.upgradeAllRegistered(); + if (handler) { + handler.upgradeAllRegistered(); return true; } return false; } + setupMaterialTextField(elementRef: ElementRef, handler: any, value: string): boolean { + if (elementRef && handler) { + let el = elementRef.nativeElement; + if (el) { + let container = el.querySelector('.mdl-textfield'); + if (container) { + container.MaterialTextfield.change(value); + return true; + } + } + } + return false; + } + checkVisibility(field: FormFieldModel) { this.fieldChanged.emit(field); } + protected getHyperlinkUrl(field: FormFieldModel) { + let url = WidgetComponent.DEFAULT_HYPERLINK_URL; + if (field && field.hyperlinkUrl) { + url = field.hyperlinkUrl; + if (!/^https?:\/\//i.test(url)) { + url = `${WidgetComponent.DEFAULT_HYPERLINK_SCHEME}${url}`; + } + } + return url; + } + + protected getHyperlinkText(field: FormFieldModel) { + if (field) { + return field.displayText || field.hyperlinkUrl; + } + return null; + } + + protected handleError(error: any) { + if (error) { + console.log(error); + } + } + } diff --git a/ng2-components/ng2-activiti-form/src/declarations.d.ts b/ng2-components/ng2-activiti-form/src/declarations.d.ts new file mode 100644 index 0000000000..562a80e4f9 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/declarations.d.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. + */ + +declare var module: any; +declare var moment: any; +declare let mdDateTimePicker: any; + +// MDL +declare var componentHandler: any; +declare let dialogPolyfill: any; diff --git a/ng2-components/ng2-activiti-form/src/i18n/en.json b/ng2-components/ng2-activiti-form/src/i18n/en.json new file mode 100644 index 0000000000..701b687e00 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/i18n/en.json @@ -0,0 +1,7 @@ +{ + "FORM": { + "START_FORM": { + "TITLE": "Start Form" + } + } +} diff --git a/ng2-components/ng2-activiti-form/src/services/activiti-alfresco.service.ts b/ng2-components/ng2-activiti-form/src/services/activiti-alfresco.service.ts new file mode 100644 index 0000000000..dec92c19e3 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/activiti-alfresco.service.ts @@ -0,0 +1,90 @@ +/*! + * @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 { ExternalContent } from '../components/widgets/core/external-content'; +import { ExternalContentLink } from '../components/widgets/core/external-content-link'; + +@Injectable() +export class ActivitiAlfrescoContentService { + + static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error'; + static GENERIC_ERROR_MESSAGE: string = 'Server error'; + + constructor(private authService: AlfrescoAuthenticationService) { + } + + /** + * Returns a list of child nodes below the specified folder + * + * @param accountId + * @param folderId + * @returns {null} + */ + getAlfrescoNodes(accountId: string, folderId: string): Observable<[ExternalContent]> { + let apiService: any = this.authService.getAlfrescoApi(); + let accountShortId = accountId.replace('alfresco-', ''); + return Observable.fromPromise(apiService.activiti.alfrescoApi.getContentInFolder(accountShortId, folderId)) + .map(this.toJsonArray) + .catch(this.handleError); + } + + /** + * Returns a list of child nodes below the specified folder + * + * @param accountId + * @param node + * @param siteId + * @returns {null} + */ + linkAlfrescoNode(accountId: string, node: ExternalContent, siteId: string): Observable<ExternalContentLink> { + let apiService: any = this.authService.getAlfrescoApi(); + return Observable.fromPromise(apiService.activiti.contentApi.createTemporaryRelatedContent({ + link: true, + name: node.title, + simpleType: node.simpleType, + source: accountId, + sourceId: node.id + '@' + siteId + })).map(this.toJson).catch(this.handleError); + } + + toJson(res: any) { + if (res) { + return res || {}; + } + return {}; + } + + toJsonArray(res: any) { + if (res) { + return res.data || []; + } + return []; + } + + handleError(error: any): Observable<any> { + let errMsg = ActivitiAlfrescoContentService.UNKNOWN_ERROR_MESSAGE; + if (error) { + errMsg = (error.message) ? error.message : + error.status ? `${error.status} - ${error.statusText}` : ActivitiAlfrescoContentService.GENERIC_ERROR_MESSAGE; + } + console.error(errMsg); + return Observable.throw(errMsg); + } +} diff --git a/ng2-components/ng2-activiti-form/src/services/assets/widget-visibility.service.mock.ts b/ng2-components/ng2-activiti-form/src/services/assets/widget-visibility.service.mock.ts new file mode 100644 index 0000000000..15c5b8cdc8 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/assets/widget-visibility.service.mock.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 { FormModel, FormValues } from '../../components/widgets/core/index'; + +export var formTest = new FormModel({}); + +export var 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'} +]; + +export var formValues: FormValues = { + 'test_1': 'value_1', + 'test_2': 'value_2', + 'test_3': 'value_1', + 'test_4': 'dropdown_id', + 'test_5': 'dropdown_label', + 'dropdown': {'id': 'dropdown_id', 'name': 'dropdown_label'} +}; + +export var 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_TEST', + name: 'FIELD_TEST', + type: 'text', + value: 'RIGHT_FORM_FIELD_VALUE', + visibilityCondition: null, + isVisible: true + }, + { + fieldType: 'FormFieldRepresentation', + id: 'FIELD_WITH_CONDITION', + name: 'FIELD_WITH_CONDITION', + type: 'text', + value: 'field_with_condition_value', + visibilityCondition: null, + isVisible: true + }, + { + fieldType: 'FormFieldRepresentation', + id: 'LEFT_FORM_FIELD_ID', + name: 'LEFT_FORM_FIELD_NAME', + type: 'text', + value: 'LEFT_FORM_FIELD_VALUE', + visibilityCondition: null, + isVisible: true + } + ], + 2: [ + { + fieldType: 'FormFieldRepresentation', + id: 'RIGHT_FORM_FIELD_ID', + name: 'RIGHT_FORM_FIELD_NAME', + type: 'text', + value: 'RIGHT_FORM_FIELD_VALUE', + visibilityCondition: null, + isVisible: true + } + ] + } + } + ] +}; diff --git a/ng2-components/ng2-activiti-form/src/services/ecm-model.service.spec.ts b/ng2-components/ng2-activiti-form/src/services/ecm-model.service.spec.ts new file mode 100644 index 0000000000..2cc8495272 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/ecm-model.service.spec.ts @@ -0,0 +1,344 @@ +/*! + * @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 { TestBed } from '@angular/core/testing'; +import { + AlfrescoAuthenticationService, + AlfrescoSettingsService, + AlfrescoApiService +} from 'ng2-alfresco-core'; +import { EcmModelService } from './ecm-model.service'; +import { Observable } from 'rxjs/Rx'; +import { FormModel } from '../components/widgets/core/form.model'; +import { HttpModule } from '@angular/http'; + +declare let jasmine: any; + +describe('EcmModelService', () => { + + let service; + + beforeAll(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + AlfrescoSettingsService, + AlfrescoApiService, + AlfrescoAuthenticationService, + EcmModelService + ] + }); + service = TestBed.get(EcmModelService); + }); + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should fetch ECM models', (done) => { + service.getEcmModels().subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('alfresco/versions/1/cmm')).toBeTruthy(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should fetch ECM types', (done) => { + + let modelName = 'modelTest'; + + service.getEcmType(modelName).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('versions/1/cmm/' + modelName + '/types')).toBeTruthy(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should create ECM types', (done) => { + + let typeName = 'typeTest'; + + service.createEcmType(typeName, service.MODEL_NAME, service.TYPE_MODEL).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('versions/1/cmm/' + service.MODEL_NAME + '/types')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual(typeName); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).title).toEqual(typeName); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).parentName).toEqual(service.TYPE_MODEL); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should create ECM types with a clean and preserv real name in the title', (done) => { + + let typeName = 'typeTest:testName@#$*!'; + let cleanName = 'testName'; + + service.createEcmType(typeName, service.MODEL_NAME, service.TYPE_MODEL).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('versions/1/cmm/' + service.MODEL_NAME + '/types')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual(cleanName); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).title).toEqual(typeName); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).parentName).toEqual(service.TYPE_MODEL); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should add propery to a type', (done) => { + + let typeName = 'typeTest'; + let formFields = { + values: { + test: 'test', + test2: 'test2' + } + }; + + service.addPropertyToAType(service.MODEL_NAME, typeName, formFields).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('1/cmm/' + service.MODEL_NAME + '/types/' + typeName + '?select=props')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual(typeName); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties).toEqual([{ + name: 'test', + title: 'test', + description: 'test', + dataType: 'd:text', + multiValued: false, + mandatory: false, + mandatoryEnforced: false + }, { + name: 'test2', + title: 'test2', + description: 'test2', + dataType: 'd:text', + multiValued: false, + mandatory: false, + mandatoryEnforced: false + }]); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should add propery to a type and clean name type', (done) => { + + let typeName = 'typeTest:testName@#$*!'; + let cleanName = 'testName'; + let formFields = { + values: { + test: 'test', + test2: 'test2' + } + }; + + service.addPropertyToAType(service.MODEL_NAME, typeName, formFields).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('1/cmm/' + service.MODEL_NAME + '/types/' + cleanName + '?select=props')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual(cleanName); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties).toEqual([{ + name: 'test', + title: 'test', + description: 'test', + dataType: 'd:text', + multiValued: false, + mandatory: false, + mandatoryEnforced: false + }, { + name: 'test2', + title: 'test2', + description: 'test2', + dataType: 'd:text', + multiValued: false, + mandatory: false, + mandatoryEnforced: false + }]); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should create ECM model', (done) => { + + service.createEcmModel(service.MODEL_NAME, service.MODEL_NAMESPACE).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('alfresco/versions/1/cmm')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).status).toEqual('DRAFT'); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should activate ECM model', (done) => { + + service.activeEcmModel(service.MODEL_NAME).subscribe(() => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('alfresco/versions/1/cmm/' + service.MODEL_NAME + '?select=status')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).status).toEqual('ACTIVE'); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should create an ECM type with properties', (done) => { + spyOn(service, 'createEcmType').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + spyOn(service, 'addPropertyToAType').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + service.createEcmTypeWithProperties('nameType', new FormModel()).subscribe(() => { + expect(service.createEcmType).toHaveBeenCalled(); + expect(service.addPropertyToAType).toHaveBeenCalled(); + done(); + }); + }); + + it('Should return the already existing type', (done) => { + spyOn(service, 'searchEcmType').and.callFake(() => { + return Observable.create(observer => { + observer.next({test: 'I-EXIST'}); + observer.complete(); + }); + }); + + spyOn(service, 'createEcmTypeWithProperties').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + service.saveFomType('nameType', new FormModel()).subscribe(() => { + expect(service.searchEcmType).toHaveBeenCalled(); + expect(service.createEcmTypeWithProperties).not.toHaveBeenCalled(); + done(); + }); + }); + + it('Should create an ECM type with properties if the ecm Type is not defined already', (done) => { + spyOn(service, 'searchEcmType').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + spyOn(service, 'createEcmTypeWithProperties').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + service.saveFomType('nameType', new FormModel()).subscribe(() => { + expect(service.searchEcmType).toHaveBeenCalled(); + expect(service.createEcmTypeWithProperties).toHaveBeenCalled(); + done(); + }); + }); + + it('Should create an ECM model for the activiti if not defined already', (done) => { + spyOn(service, 'searchActivitiEcmModel').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + spyOn(service, 'createActivitiEcmModel').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + service.createEcmTypeForActivitiForm('nameType', new FormModel()).subscribe(() => { + expect(service.searchActivitiEcmModel).toHaveBeenCalled(); + expect(service.createActivitiEcmModel).toHaveBeenCalled(); + done(); + }); + }); + + it('If a model for the activiti is already define has to save the new type', (done) => { + spyOn(service, 'searchActivitiEcmModel').and.callFake(() => { + return Observable.create(observer => { + observer.next({test: 'I-EXIST'}); + observer.complete(); + }); + }); + + spyOn(service, 'saveFomType').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + service.createEcmTypeForActivitiForm('nameType', new FormModel()).subscribe(() => { + expect(service.searchActivitiEcmModel).toHaveBeenCalled(); + expect(service.saveFomType).toHaveBeenCalled(); + done(); + }); + }); +}); 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 index 9c9f4437d3..72acfa1413 100644 --- a/ng2-components/ng2-activiti-form/src/services/ecm-model.service.ts +++ b/ng2-components/ng2-activiti-form/src/services/ecm-model.service.ts @@ -20,7 +20,6 @@ import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfr 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 { @@ -31,13 +30,12 @@ export class EcmModelService { constructor(private authService: AlfrescoAuthenticationService, private http: Http, - public alfrescoSettingsService: AlfrescoSettingsService, - private nodeService: NodeService) { + public alfrescoSettingsService: AlfrescoSettingsService) { } public createEcmTypeForActivitiForm(formName: string, form: FormModel): Observable<any> { return Observable.create(observer => { - this.seachActivitiEcmModel().subscribe( + this.searchActivitiEcmModel().subscribe( model => { if (!model) { this.createActivitiEcmModel(formName, form).subscribe(typeForm => { @@ -45,7 +43,7 @@ export class EcmModelService { observer.complete(); }); } else { - this.createFomType(formName, form).subscribe(typeForm => { + this.saveFomType(formName, form).subscribe(typeForm => { observer.next(typeForm); observer.complete(); }); @@ -57,13 +55,13 @@ export class EcmModelService { } - private seachActivitiEcmModel() { + searchActivitiEcmModel() { 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> { + createActivitiEcmModel(formName: string, form: FormModel): Observable<any> { return Observable.create(observer => { this.createEcmModel(EcmModelService.MODEL_NAME, EcmModelService.MODEL_NAMESPACE).subscribe( model => { @@ -84,7 +82,7 @@ export class EcmModelService { }); } - private createFomType(formName: string, form: FormModel): Observable<any> { + saveFomType(formName: string, form: FormModel): Observable<any> { return Observable.create(observer => { this.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe( ecmType => { @@ -130,8 +128,6 @@ export class EcmModelService { 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 @@ -143,8 +139,6 @@ export class EcmModelService { 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: '' }; @@ -165,7 +159,6 @@ export class EcmModelService { .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(); 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 index a93a0abe25..c8ca4ca711 100644 --- a/ng2-components/ng2-activiti-form/src/services/form.service.spec.ts +++ b/ng2-components/ng2-activiti-form/src/services/form.service.spec.ts @@ -15,34 +15,41 @@ * 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 { TestBed } from '@angular/core/testing'; +import { + AlfrescoAuthenticationService, + AlfrescoSettingsService, + AlfrescoApiService +} from 'ng2-alfresco-core'; import { FormService } from './form.service'; import { EcmModelService } from './ecm-model.service'; -import { NodeService } from './node.service'; +import { FormDefinitionModel } from '../models/form-definition.model'; +import { HttpModule, Response, ResponseOptions } from '@angular/http'; declare let jasmine: any; describe('FormService', () => { - let responseBody: any, formService: FormService; + let responseBody: any, service: FormService, apiService: AlfrescoApiService; - beforeEachProviders(() => { - return [ - FormService, - AlfrescoSettingsService, - AlfrescoAuthenticationService, - EcmModelService, - HTTP_PROVIDERS, - NodeService - ]; + beforeAll(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + AlfrescoAuthenticationService, + AlfrescoSettingsService, + EcmModelService, + AlfrescoApiService, + FormService + ] + }); + service = TestBed.get(FormService); + apiService = TestBed.get(AlfrescoApiService); }); - beforeEach(inject([FormService], (service: FormService) => { + beforeEach(() => { jasmine.Ajax.install(); - formService = service; - })); + }); afterEach(() => { jasmine.Ajax.uninstall(); @@ -56,7 +63,7 @@ describe('FormService', () => { ] }; - formService.getProcessDefinitions().subscribe(result => { + service.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(); @@ -72,12 +79,12 @@ describe('FormService', () => { it('should fetch and parse tasks', (done) => { responseBody = { data: [ - { id: '1' }, - { id: '2' } + {id: '1'}, + {id: '2'} ] }; - formService.getTasks().subscribe(result => { + service.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(); @@ -95,7 +102,7 @@ describe('FormService', () => { id: '1' }; - formService.getTask('1').subscribe(result => { + service.getTask('1').subscribe(result => { expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/1')).toBeTruthy(); expect(result.id).toEqual(responseBody.id); done(); @@ -114,7 +121,7 @@ describe('FormService', () => { field2: 'two' }; - formService.saveTaskForm('1', values).subscribe(() => { + service.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); @@ -134,7 +141,7 @@ describe('FormService', () => { field2: 'two' }; - formService.completeTaskForm('1', values).subscribe(() => { + service.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); @@ -154,10 +161,10 @@ describe('FormService', () => { field2: 'two' }; - formService.completeTaskForm('1', values, 'custom').subscribe(() => { + service.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' ); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).outcome).toEqual('custom'); done(); }); @@ -170,9 +177,9 @@ describe('FormService', () => { }); it('should get task form by id', (done) => { - responseBody = { id: 1 }; + responseBody = {id: 1}; - formService.getTaskForm('1').subscribe(result => { + service.getTaskForm('1').subscribe(result => { expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy(); expect(result.id).toEqual(responseBody.id); done(); @@ -186,9 +193,9 @@ describe('FormService', () => { }); it('should get form definition by id', (done) => { - responseBody = { id: 1 }; + responseBody = {id: 1}; - formService.getFormDefinitionById('1').subscribe(result => { + service.getFormDefinitionById('1').subscribe(result => { expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/form-models/1')).toBeTruthy(); expect(result.id).toEqual(responseBody.id); done(); @@ -206,11 +213,11 @@ describe('FormService', () => { const formId = 1; responseBody = { data: [ - { id: formId } + {id: formId} ] }; - formService.getFormDefinitionByName(formName).subscribe(result => { + service.getFormDefinitionByName(formName).subscribe(result => { expect(jasmine.Ajax.requests.mostRecent().url.endsWith(`models?filter=myReusableForms&filterText=${formName}&modelType=2`)).toBeTruthy(); expect(result).toEqual(formId); done(); @@ -223,36 +230,52 @@ describe('FormService', () => { }); }); + it('should get start form definition by process definition id', (done) => { + + let processApiSpy = jasmine.createSpyObj(['getProcessDefinitionStartForm']); + spyOn(apiService, 'getInstance').and.returnValue({ + activiti: { + processApi: processApiSpy + } + }); + processApiSpy.getProcessDefinitionStartForm.and.returnValue(Promise.resolve({ id: '1' })); + + service.getStartFormDefinition('myprocess:1').subscribe(result => { + expect(processApiSpy.getProcessDefinitionStartForm).toHaveBeenCalledWith('myprocess:1'); + done(); + }); + }); + it('should not get form id from response', () => { - let response = new Response(new ResponseOptions({ body: null })); - expect(formService.getFormId(response)).toBeNull(); + let response = new Response(new ResponseOptions({body: null})); + expect(service.getFormId(response)).toBeNull(); - response = new Response(new ResponseOptions({ body: {} })); - expect(formService.getFormId(response)).toBeNull(); + response = new Response(new ResponseOptions({body: {}})); + expect(service.getFormId(response)).toBeNull(); - response = new Response(new ResponseOptions({ body: { data: null } })); - expect(formService.getFormId(response)).toBeNull(); + response = new Response(new ResponseOptions({body: {data: null}})); + expect(service.getFormId(response)).toBeNull(); - response = new Response(new ResponseOptions({ body: { data: [] } })); - expect(formService.getFormId(response)).toBeNull(); + response = new Response(new ResponseOptions({body: {data: []}})); + expect(service.getFormId(response)).toBeNull(); - expect(formService.getFormId(null)).toBeNull(); + expect(service.getFormId(null)).toBeNull(); }); it('should fallback to empty json array', () => { - expect(formService.toJsonArray(null)).toEqual([]); + expect(service.toJsonArray(null)).toEqual([]); - let response = new Response(new ResponseOptions({ body: {} })); - expect(formService.toJsonArray(response)).toEqual([]); + let response = new Response(new ResponseOptions({body: {}})); + expect(service.toJsonArray(response)).toEqual([]); - response = new Response(new ResponseOptions({ body: { data: null } })); - expect(formService.toJsonArray(response)).toEqual([]); + response = new Response(new ResponseOptions({body: {data: null}})); + expect(service.toJsonArray(response)).toEqual([]); }); it('should handle error with generic message', () => { spyOn(console, 'error').and.stub(); - formService.handleError(null); + service.handleError(null); expect(console.error).toHaveBeenCalledWith(FormService.UNKNOWN_ERROR_MESSAGE); }); @@ -260,14 +283,14 @@ describe('FormService', () => { spyOn(console, 'error').and.stub(); const message = '<error>'; - formService.handleError({ message: message }); + service.handleError({message: message}); expect(console.error).toHaveBeenCalledWith(message); }); it('should handle error with detailed message', () => { spyOn(console, 'error').and.stub(); - formService.handleError({ + service.handleError({ status: '400', statusText: 'Bad request' }); @@ -276,7 +299,141 @@ describe('FormService', () => { it('should handle error with generic message', () => { spyOn(console, 'error').and.stub(); - formService.handleError({}); + service.handleError({}); expect(console.error).toHaveBeenCalledWith(FormService.GENERIC_ERROR_MESSAGE); }); + + it('should get all the forms with modelType=2', (done) => { + responseBody = {}; + + service.getForms().subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy(); + expect(result).toEqual(responseBody); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should search for Form with modelType=2', (done) => { + responseBody = {data: [{id: 1, name: 'findme'}, {id: 2, name: 'testform'}]}; + + service.searchFrom('findme').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy(); + expect(result.name).toEqual('findme'); + expect(result.id).toEqual(1); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should create a Form with modelType=2', (done) => { + responseBody = {id: 1, modelType: 'test'}; + + service.createForm('testName').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/models')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).modelType).toEqual(2); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual('testName'); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should add form fields to a form', (done) => { + responseBody = {id: 1, modelType: 'test'}; + + let formId = '100'; + let name = 'testName'; + let data = [{name: 'name'}, {name: 'email'}]; + let formDefinitionModel = new FormDefinitionModel(formId, name, 'testUserName', '2016-09-05T14:41:19.049Z', data); + + service.addFieldsToAForm(formId, formDefinitionModel).subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/form-models/' + formId)).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).formRepresentation.name).toEqual(name); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('should create a Form form a Node', (done) => { + + let nameForm = 'testNode'; + + responseBody = {id: 1, modelType: 'test'}; + + let formId = 100; + + stubCreateForm(); + + stubGetEcmModel(); + + stubAddFieldsToAForm(); + + service.createFormFromANode(nameForm).subscribe(result => { + expect(result.id).toEqual(formId); + done(); + }); + + function stubCreateForm() { + jasmine.Ajax.stubRequest( + 'http://localhost:9999/activiti-app/api/enterprise/models' + ).andReturn({ + status: 200, + statusText: 'HTTP/1.1 200 OK', + contentType: 'text/xml;charset=UTF-8', + responseText: {id: formId, name: 'test', lastUpdatedByFullName: 'uset', lastUpdated: '12-12-2016'} + }); + } + + function stubGetEcmModel() { + jasmine.Ajax.stubRequest( + 'http://localhost:8080/alfresco/api/-default-/private/alfresco/versions/1/cmm/activitiFormsModel/types' + ).andReturn({ + status: 200, + statusText: 'HTTP/1.1 200 OK', + contentType: 'text/xml;charset=UTF-8', + responseText: { + list: { + entries: [{ + entry: { + prefixedName: nameForm, + title: nameForm, + properties: [{name: 'name'}, {name: 'email'}] + } + }, {entry: {prefixedName: 'notme', title: 'notme'}}] + } + } + }); + } + + function stubAddFieldsToAForm() { + jasmine.Ajax.stubRequest( + 'http://localhost:9999/activiti-app/api/enterprise/editor/form-models/' + formId + ).andReturn({ + status: 200, + statusText: 'HTTP/1.1 200 OK', + contentType: 'text/xml;charset=UTF-8', + responseText: {id: formId, name: 'test', lastUpdatedByFullName: 'user', lastUpdated: '12-12-2016'} + }); + } + }); }); diff --git a/ng2-components/ng2-activiti-form/src/services/form.service.ts b/ng2-components/ng2-activiti-form/src/services/form.service.ts index a5f33bc003..5370353656 100644 --- a/ng2-components/ng2-activiti-form/src/services/form.service.ts +++ b/ng2-components/ng2-activiti-form/src/services/form.service.ts @@ -17,10 +17,12 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Rx'; -import { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { AlfrescoApiService } from 'ng2-alfresco-core'; import { FormValues } from './../components/widgets/core/index'; import { FormDefinitionModel } from '../models/form-definition.model'; import { EcmModelService } from './ecm-model.service'; +import { GroupModel } from './../components/widgets/core/group.model'; +import { GroupUserModel } from './../components/widgets/core/group-user.model'; @Injectable() export class FormService { @@ -28,22 +30,22 @@ export class FormService { static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error'; static GENERIC_ERROR_MESSAGE: string = 'Server error'; - constructor(private authService: AlfrescoAuthenticationService, - private ecmModelService: EcmModelService) { + constructor(private ecmModelService: EcmModelService, + private apiService: AlfrescoApiService) { } /** * Create a Form with a fields for each metadata properties * @returns {Observable<any>} */ - public createFormFromNodeType(formName: string): Observable<any> { + createFormFromANode(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 => { + this.addFieldsToAForm(form.id, formDefinitionModel).subscribe(formData => { observer.next(formData); observer.complete(); }, this.handleError); @@ -57,7 +59,7 @@ export class FormService { * Create a Form * @returns {Observable<any>} */ - public createForm(formName: string): Observable<any> { + createForm(formName: string): Observable<any> { let dataModel = { name: formName, description: '', @@ -65,35 +67,27 @@ export class FormService { 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); + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.createModel(dataModel)); } /** * 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)); + addFieldsToAForm(formId: string, formModel: FormDefinitionModel): Observable<any> { + return Observable.fromPromise(this.apiService.getInstance().activiti.editorApi.saveForm(formId, formModel)); } /** * Search For A Form by name * @returns {Observable<any>} */ - public searchFrom(name: string): Observable<any> { + searchFrom(name: string): Observable<any> { let opts = { 'modelType': 2 }; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)).map(function (forms: any) { + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.getModels(opts)).map(function (forms: any) { return forms.data.find(formdata => formdata.name === name); }).catch(this.handleError); } @@ -102,20 +96,20 @@ export class FormService { * Get All the forms * @returns {Observable<any>} */ - public getForms(): Observable<any> { + getForms(): Observable<any> { let opts = { 'modelType': 2 }; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)); + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.getModels(opts)); } /** * Get Process Definition * @returns {Observable<any>} */ - public getProcessDefinitions(): Observable<any> { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessDefinitions({})) + getProcessDefinitions(): Observable<any> { + return Observable.fromPromise(this.apiService.getInstance().activiti.processApi.getProcessDefinitions({})) .map(this.toJsonArray) .catch(this.handleError); } @@ -125,8 +119,8 @@ export class FormService { * @param taskId Task Id * @returns {Observable<any>} */ - public getTasks(): Observable<any> { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.listTasks({})) + getTasks(): Observable<any> { + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.listTasks({})) .map(this.toJsonArray) .catch(this.handleError); } @@ -136,8 +130,8 @@ export class FormService { * @param taskId Task Id * @returns {Observable<any>} */ - public getTask(taskId: string): Observable<any> { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTask(taskId)) + getTask(taskId: string): Observable<any> { + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTask(taskId)) .map(this.toJson) .catch(this.handleError); } @@ -148,10 +142,10 @@ export class FormService { * @param formValues Form Values * @returns {Observable<any>} */ - public saveTaskForm(taskId: string, formValues: FormValues): Observable<any> { - let body = JSON.stringify({values: formValues}); + saveTaskForm(taskId: string, formValues: FormValues): Observable<any> { + let body = JSON.stringify({ values: formValues }); - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.saveTaskForm(taskId, body)) + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.saveTaskForm(taskId, body)) .catch(this.handleError); } @@ -162,14 +156,14 @@ export class FormService { * @param outcome Form Outcome * @returns {Observable<any>} */ - public completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> { - let data: any = {values: formValues}; + 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)) + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.completeTaskForm(taskId, body)) .catch(this.handleError); } @@ -178,8 +172,8 @@ export class FormService { * @param taskId Task Id * @returns {Observable<any>} */ - public getTaskForm(taskId: string): Observable<any> { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTaskForm(taskId)) + getTaskForm(taskId: string): Observable<any> { + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTaskForm(taskId)) .map(this.toJson) .catch(this.handleError); } @@ -189,8 +183,8 @@ export class FormService { * @param formId Form Id * @returns {Observable<any>} */ - public getFormDefinitionById(formId: string): Observable<any> { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.editorApi.getForm(formId)) + getFormDefinitionById(formId: string): Observable<any> { + return Observable.fromPromise(this.apiService.getInstance().activiti.editorApi.getForm(formId)) .map(this.toJson) .catch(this.handleError); } @@ -200,18 +194,127 @@ export class FormService { * @param name * @returns {Promise<T>|Promise<ErrorObservable>} */ - public getFormDefinitionByName(name: string): Observable<any> { + getFormDefinitionByName(name: string): Observable<any> { let opts = { 'filter': 'myReusableForms', 'filterText': name, 'modelType': 2 }; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)) + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.getModels(opts)) .map(this.getFormId) .catch(this.handleError); } + /** + * Get start form instance for a given processId + * @param processId Process definition ID + * @returns {Observable<any>} + */ + getStartFormInstance(processId: string): Observable<any> { + return Observable.fromPromise( + this.apiService.getInstance().activiti.processApi.getProcessInstanceStartForm(processId)) + .map(this.toJson) + .catch(this.handleError); + } + + /** + * Get start form definition for a given process + * @param processId Process definition ID + * @returns {Observable<any>} + */ + getStartFormDefinition(processId: string): Observable<any> { + return Observable.fromPromise( + this.apiService.getInstance().activiti.processApi.getProcessDefinitionStartForm(processId)) + .map(this.toJson) + .catch(this.handleError); + } + + getRestFieldValues(taskId: string, field: string): Observable<any> { + let alfrescoApi = this.apiService.getInstance(); + return Observable.fromPromise(alfrescoApi.activiti.taskApi.getRestFieldValues(taskId, field)); + } + + getRestFieldValuesByProcessId(processDefinitionId: string, field: string): Observable<any> { + let alfrescoApi = this.apiService.getInstance(); + return Observable.fromPromise(alfrescoApi.activiti.processApi.getRestFieldValues(processDefinitionId, field)); + } + + getRestFieldValuesColumnByProcessId(processDefinitionId: string, field: string, column?: string): Observable<any> { + let alfrescoApi = this.apiService.getInstance(); + return Observable.fromPromise(alfrescoApi.activiti.processApi.getRestTableFieldValues(processDefinitionId, field, column)); + } + + getRestFieldValuesColumn(taskId: string, field: string, column?: string): Observable<any> { + let alfrescoApi = this.apiService.getInstance(); + return Observable.fromPromise(alfrescoApi.activiti.taskApi.getRestFieldValuesColumn(taskId, field, column)); + } + + // TODO: uses private webApp api + getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> { + return Observable.create(observer => { + + let xhr: XMLHttpRequest = new XMLHttpRequest(); + xhr.withCredentials = true; + + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let json = JSON.parse(xhr.response); + let data: GroupModel[] = (json.data || []).map(item => <GroupModel> item); + // console.log(json); + observer.next(data); + observer.complete(); + } else { + console.error(xhr.response); + Observable.throw(new Error(xhr.response)); + } + } + }; + + let host = this.apiService.getInstance().config.hostBpm; + let url = `${host}/activiti-app/app/rest/workflow-groups?filter=${filter}`; + if (groupId) { + url += `&groupId=${groupId}`; + } + xhr.open('GET', url, true); + xhr.setRequestHeader('Authorization', this.apiService.getInstance().getTicketBpm()); + xhr.send(); + }); + } + + getWorkflowUsers(filter: string, groupId?: string): Observable<GroupUserModel[]> { + return Observable.create(observer => { + + let xhr: XMLHttpRequest = new XMLHttpRequest(); + xhr.withCredentials = true; + + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let json = JSON.parse(xhr.response); + let data: GroupUserModel[] = (json.data || []).map(item => <GroupUserModel> item); + // console.log(json); + observer.next(data); + observer.complete(); + } else { + console.error(xhr.response); + Observable.throw(new Error(xhr.response)); + } + } + }; + + let host = this.apiService.getInstance().config.hostBpm; + let url = `${host}/activiti-app/app/rest/workflow-users?filter=${filter}`; + if (groupId) { + url += `&groupId=${groupId}`; + } + xhr.open('GET', url, true); + xhr.setRequestHeader('Authorization', this.apiService.getInstance().getTicketBpm()); + xhr.send(); + }); + } + getFormId(res: any) { let result = null; diff --git a/ng2-components/ng2-activiti-form/src/services/node.service.spec.ts b/ng2-components/ng2-activiti-form/src/services/node.service.spec.ts new file mode 100644 index 0000000000..ba7dbd740a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/services/node.service.spec.ts @@ -0,0 +1,178 @@ +/*! + * @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 { TestBed } from '@angular/core/testing'; +import { + AlfrescoAuthenticationService, + AlfrescoSettingsService, + AlfrescoApiService +} from 'ng2-alfresco-core'; +import { NodeService } from './node.service'; +import { NodeMetadata } from '../models/node-metadata.model'; +import { HttpModule } from '@angular/http'; +import { EcmModelService } from './ecm-model.service'; + +declare let jasmine: any; + +describe('NodeService', () => { + + let service: NodeService; + + beforeAll(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + AlfrescoAuthenticationService, + AlfrescoSettingsService, + NodeService, + AlfrescoApiService, + EcmModelService + ] + }); + service = TestBed.get(NodeService); + }); + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Should fetch and node metadata', (done) => { + let responseBody = { + entry: { + id: '111-222-33-44-1123', + nodeType: 'typeTest', + properties: { + test: 'test', + testdata: 'testdata' + } + } + }; + + service.getNodeMetadata('-nodeid-').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('nodes/-nodeid-')).toBeTruthy(); + let node = new NodeMetadata({ + test: 'test', + testdata: 'testdata' + }, 'typeTest'); + expect(result).toEqual(node); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('Should clean the metadata from :', (done) => { + let responseBody = { + entry: { + id: '111-222-33-44-1123', + nodeType: 'typeTest', + properties: { + 'metadata:test': 'test', + 'metadata:testdata': 'testdata' + } + } + }; + + service.getNodeMetadata('-nodeid-').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('nodes/-nodeid-')).toBeTruthy(); + let node = new NodeMetadata({ + test: 'test', + testdata: 'testdata' + }, 'typeTest'); + expect(result).toEqual(node); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('Should create a node with metadata', (done) => { + let data = { + test: 'test', + testdata: 'testdata' + }; + + let responseBody = { + id: 'a74d91fb-ea8a-4812-ad98-ad878366b5be', + isFile: false, + isFolder: true + }; + + service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary', 'testNode').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy(); + expect(result).toEqual(responseBody); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(responseBody) + }); + }); + + it('Should add activitiForms suffix to the metadata properties', (done) => { + let data = { + test: 'test', + testdata: 'testdata' + }; + + service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties[EcmModelService.MODEL_NAMESPACE + ':test']).toBeDefined(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties[EcmModelService.MODEL_NAMESPACE + ':testdata']).toBeDefined(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); + + it('Should assign an UUID to the name when name not passed', (done) => { + let data = { + test: 'test', + testdata: 'testdata' + }; + + service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary').subscribe(result => { + expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy(); + expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toBeDefined(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({}) + }); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/services/node.service.ts b/ng2-components/ng2-activiti-form/src/services/node.service.ts index 10a9c39815..860cdbb1c2 100644 --- a/ng2-components/ng2-activiti-form/src/services/node.service.ts +++ b/ng2-components/ng2-activiti-form/src/services/node.service.ts @@ -70,7 +70,9 @@ export class NodeService { relativePath: path }; - return Observable.fromPromise(this.authService.getAlfrescoApi().nodes.addNode('-root-', body, {})); + // TODO: requires update to alfresco-js-api typings + let apiService: any = this.authService.getAlfrescoApi(); + return Observable.fromPromise(apiService.nodes.addNode('-root-', body, {})); } private generateUuid() { @@ -86,7 +88,11 @@ export class NodeService { if (data && data.properties) { for (let key in data.properties) { if (key) { - metadata [key.split(':')[1]] = data.properties[key]; + if (key.indexOf(':') !== -1) { + metadata [key.split(':')[1]] = data.properties[key]; + } else { + metadata [key] = data.properties[key]; + } } } } 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 index 3e1b9f62b8..ffd7a8e177 100644 --- 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 @@ -15,543 +15,678 @@ * limitations under the License. */ -import { it, describe, inject, beforeEach, beforeEachProviders } from '@angular/core/testing'; +import { HttpModule } from '@angular/http'; +import { TestBed } from '@angular/core/testing'; +import { + formTest, + fakeTaskProcessVariableModels, + formValues, + fakeFormJson +} from './assets/widget-visibility.service.mock'; 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 { AlfrescoSettingsService, AlfrescoAuthenticationService, AlfrescoApiService } from 'ng2-alfresco-core'; import { TaskProcessVariableModel } from '../models/task-process-variable.model'; -import { FormModel, FormValues, FormFieldModel } from '../components/widgets/core/index'; +import { WidgetVisibilityModel } from '../models/widget-visibility.model'; +import { FormModel, FormFieldModel, TabModel, ContainerModel, FormFieldTypes } 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 service: WidgetVisibilityService; + let booleanResult: boolean; + let stubFormWithFields = new FormModel(fakeFormJson); - 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 - ]; + beforeAll(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + AlfrescoSettingsService, + AlfrescoAuthenticationService, + AlfrescoApiService, + WidgetVisibilityService + ] + }); + service = TestBed.get(WidgetVisibilityService); }); - beforeEach( - inject([WidgetVisibilityService], (activitiService: WidgetVisibilityService) => { - jasmine.Ajax.install(); - service = activitiService; - }) - ); + beforeEach(() => { + jasmine.Ajax.install(); + }); 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(); - } - ); + describe('should be able to evaluate logic operations', () => { - jasmine.Ajax.requests.mostRecent().respondWith({ - 'status': 200, - contentType: 'application/json', - responseText: JSON.stringify(fakeTaskProcessVariableModels) + it('using AND and return true', () => { + booleanResult = service.evaluateLogicalOperation('and', true, true); + expect(booleanResult).toBeTruthy(); + }); + + it('using AND and return false', () => { + booleanResult = service.evaluateLogicalOperation('and', true, false); + expect(booleanResult).toBeFalsy(); + }); + + it('using OR and return true', () => { + booleanResult = service.evaluateLogicalOperation('or', true, false); + expect(booleanResult).toBeTruthy(); + }); + + it('using OR and return false', () => { + booleanResult = service.evaluateLogicalOperation('or', false, false); + expect(booleanResult).toBeFalsy(); + }); + + it('using AND NOT and return true', () => { + booleanResult = service.evaluateLogicalOperation('and-not', true, false); + expect(booleanResult).toBeTruthy(); + }); + + it('using AND NOT and return false', () => { + booleanResult = service.evaluateLogicalOperation('and-not', false, false); + expect(booleanResult).toBeFalsy(); + }); + + it('using OR NOT and return true', () => { + booleanResult = service.evaluateLogicalOperation('or-not', true, true); + expect(booleanResult).toBeTruthy(); + }); + + it('using OR NOT and return false', () => { + booleanResult = service.evaluateLogicalOperation('or-not', false, true); + expect(booleanResult).toBeFalsy(); + }); + + it('should fail with invalid operation', () => { + booleanResult = service.evaluateLogicalOperation(undefined, false, true); + expect(booleanResult).toBeUndefined(); }); }); - it('should evaluate logic operation for two values', () => { - let res: boolean; + describe('should be able to evaluate next condition operations', () => { - res = service.evaluateLogicalOperation( 'or', true, false); + it('using == and return true', () => { + booleanResult = service.evaluateCondition('test', 'test', '=='); + expect(booleanResult).toBeTruthy(); + }); - expect(res).toBeTruthy(); + it('using < and return true', () => { + booleanResult = service.evaluateCondition(1, 2, '<'); + expect(booleanResult).toBeTruthy(); + }); - res = service.evaluateLogicalOperation( 'and', true, true); + it('using != and return true', () => { + booleanResult = service.evaluateCondition(true, false, '!='); + expect(booleanResult).toBeTruthy(); + }); - expect(res).toBeTruthy(); + it('using != and return false', () => { + booleanResult = service.evaluateCondition(true, true, '!='); + expect(booleanResult).toBeFalsy(); + }); - res = service.evaluateLogicalOperation( 'and not', true, false); + it('using >= and return true', () => { + booleanResult = service.evaluateCondition(2, 2, '>='); + expect(booleanResult).toBeTruthy(); + }); - expect(res).toBeTruthy(); + it('using empty with null values and return true', () => { + booleanResult = service.evaluateCondition(null, null, 'empty'); + expect(booleanResult).toBeTruthy(); + }); - res = service.evaluateLogicalOperation( 'or not', true, true ); + it('using empty with empty strings values and return true', () => { + booleanResult = service.evaluateCondition('', '', 'empty'); + expect(booleanResult).toBeTruthy(); + }); - expect(res).toBeTruthy(); + it('using empty with empty string value and return false', () => { + booleanResult = service.evaluateCondition('fake_value', undefined, 'empty'); + expect(booleanResult).toBeFalsy(); + }); - res = service.evaluateLogicalOperation( 'or', false, false ); + it('using > and return false', () => { + booleanResult = service.evaluateCondition(2, 3, '>'); + expect(booleanResult).toBeFalsy(); + }); - expect(res).toBeFalsy(); + it('using not empty with null values and return false', () => { + booleanResult = service.evaluateCondition(null, null, '!empty'); + expect(booleanResult).toBeFalsy(); + }); - res = service.evaluateLogicalOperation( 'and', true, false ); + it('using OR NOT with empty strings and return false', () => { + booleanResult = service.evaluateCondition('', '', '!empty'); + expect(booleanResult).toBeFalsy(); + }); - expect(res).toBeFalsy(); + it('using <= and return false', () => { + booleanResult = service.evaluateCondition(2, 1, '<='); + expect(booleanResult).toBeFalsy(); + }); - res = service.evaluateLogicalOperation( 'and not', false, false ); + it('using <= and return true for different values', () => { + booleanResult = service.evaluateCondition(1, 2, '<='); + expect(booleanResult).toBeTruthy(); + }); - expect(res).toBeFalsy(); + it('using <= and return true for same values', () => { + booleanResult = service.evaluateCondition(2, 2, '<='); + expect(booleanResult).toBeTruthy(); + }); - res = service.evaluateLogicalOperation( 'or not', false, true ); - - expect(res).toBeFalsy(); + it('should return undefined for invalid operation', () => { + booleanResult = service.evaluateCondition(null, null, undefined); + expect(booleanResult).toBeUndefined(); + }); }); - it('should evaluate string operation for two values', () => { - let res: boolean; + describe('should retrive the process variables', () => { + let fakeFormWithField = new FormModel(fakeFormJson); + let visibilityObjTest: WidgetVisibilityModel; + let chainedVisibilityObj = new WidgetVisibilityModel(); - res = service.evaluateCondition( 'test', 'test', '=='); + beforeEach(() => { + visibilityObjTest = new WidgetVisibilityModel(); + }); - expect(res).toBeTruthy(); + it('should return the process variables for task', (done) => { + service.getTaskProcessVariable('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: 'json', + responseText: fakeTaskProcessVariableModels + }); + }); - res = service.evaluateCondition( 1, 2, '<'); + it('should be able to retrieve the value of a process variable', (done) => { + service.getTaskProcessVariable('9999').subscribe( + (res: TaskProcessVariableModel[]) => { + expect(res).toBeDefined(); + let varValue = service.getVariableValue(formTest, 'TEST_VAR_1', res); + expect(varValue).not.toBeUndefined(); + expect(varValue).toBe('test_value_1'); + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: fakeTaskProcessVariableModels + }); + }); - expect(res).toBeTruthy(); + it('should return undefined if the variable does not exist', (done) => { + service.getTaskProcessVariable('9999').subscribe( + (res: TaskProcessVariableModel[]) => { + let varValue = service.getVariableValue(formTest, 'TEST_MYSTERY_VAR', res); + expect(varValue).toBeUndefined(); + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: fakeTaskProcessVariableModels + }); + }); - res = service.evaluateCondition( true, false, '!=' ); + it('should retrieve the value for the right field when it is a process variable', (done) => { + service.getTaskProcessVariable('9999').subscribe( + (res: TaskProcessVariableModel[]) => { + visibilityObjTest.rightRestResponseId = 'TEST_VAR_2'; + let rightValue = service.getRightValue(formTest, visibilityObjTest); - expect(res).toBeTruthy(); + expect(rightValue).not.toBeNull(); + expect(rightValue).toBe('test_value_2'); + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: fakeTaskProcessVariableModels + }); + }); - res = service.evaluateCondition( 2, 3, '>' ); + it('should retrieve the value for the left field when it is a process variable', (done) => { + service.getTaskProcessVariable('9999').subscribe( + (res: TaskProcessVariableModel[]) => { + visibilityObjTest.leftRestResponseId = 'TEST_VAR_2'; + let rightValue = service.getLeftValue(formTest, visibilityObjTest); - expect(res).toBeFalsy(); + expect(rightValue).not.toBeNull(); + expect(rightValue).toBe('test_value_2'); + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: fakeTaskProcessVariableModels + }); + }); - res = service.evaluateCondition( 2, 2, '>=' ); + it('should evaluate the visibility for the field between form value and process var', (done) => { + service.getTaskProcessVariable('9999').subscribe( + (res: TaskProcessVariableModel[]) => { + visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightRestResponseId = 'TEST_VAR_2'; + let isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest); - expect(res).toBeTruthy(); + expect(isVisible).toBeTruthy(); + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: fakeTaskProcessVariableModels + }); + }); - res = service.evaluateCondition( 4, 2, '<=' ); + it('should evaluate visibility with multiple conditions', (done) => { + service.getTaskProcessVariable('9999').subscribe( + (res: TaskProcessVariableModel[]) => { + 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; - expect(res).toBeFalsy(); + let isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest); - res = service.evaluateCondition( null, null, 'empty' ); + expect(isVisible).toBeTruthy(); + done(); + } + ); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: fakeTaskProcessVariableModels + }); + }); - expect(res).toBeTruthy(); + it('should catch error on 403 response', (done) => { + service.getTaskProcessVariable('9999').subscribe(() => { + }, () => { + done(); + }); - res = service.evaluateCondition( '', '', 'empty' ); - - expect(res).toBeTruthy(); - - res = service.evaluateCondition( null, null, '!empty' ); - - expect(res).toBeFalsy(); - - res = service.evaluateCondition( '', '', '!empty' ); - - expect(res).toBeFalsy(); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 403 + }); + }); }); - 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'; + describe('should return the value of the field', () => { + let visibilityObjTest: WidgetVisibilityModel; + let fakeFormWithField = new FormModel(fakeFormJson); + let jsonFieldFake = { + id: 'FAKE_FORM_FIELD_ID', + value: 'FAKE_FORM_FIELD_VALUE', + visibilityCondition: undefined + }; + let fakeForm = new FormModel({ + variables: [ + { + name: 'FORM_VARIABLE_TEST', + type: 'string', + value: 'form_value_test' + }] + }); + + beforeEach(() => { + visibilityObjTest = new WidgetVisibilityModel(); + formTest.values = formValues; + jsonFieldFake.visibilityCondition = visibilityObjTest; + }); + + it('should be able to retrieve a field value searching in the form', () => { + let formValue = service.searchForm(stubFormWithFields, 'FIELD_WITH_CONDITION'); + + expect(formValue).not.toBeNull(); + expect(formValue).toBe('field_with_condition_value'); + }); + + it('should return undefined if the field value is not in the form', () => { + let formValue = service.searchForm(stubFormWithFields, 'FIELD_MYSTERY'); + + expect(formValue).toBeUndefined(); + }); + + it('should search in the form if element value is not in form values', () => { + let value = service.getFormValue(fakeFormWithField, 'FIELD_WITH_CONDITION'); + + expect(value).not.toBeNull(); + expect(value).toBe('field_with_condition_value'); + }); + + it('should return undefined if the element is not present anywhere', () => { + let formValue = service.getFormValue(fakeFormWithField, 'FIELD_MYSTERY'); + + expect(formValue).toBeUndefined(); + }); + + it('should retrieve the value for the right field when it is a value', () => { + visibilityObjTest.rightValue = '100'; + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).toBe('100'); + }); + + it('should return formatted date when right value is a date', () => { + visibilityObjTest.rightValue = '9999-12-31'; + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).toBe('9999-12-31T00:00:00.000Z'); + }); + + it('should return the value when right value is not a date', () => { + visibilityObjTest.rightValue = '9999-99-99'; + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).toBe('9999-99-99'); + }); + + it('should retrieve the value for the right field when it is a form variable', () => { + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + let rightValue = service.getRightValue(fakeFormWithField, visibilityObjTest); + + expect(rightValue).not.toBeNull(); + expect(rightValue).toBe('RIGHT_FORM_FIELD_VALUE'); + }); + + it('should take the value from form values if it is present', () => { + let formValue = service.getFormValue(formTest, 'test_1'); + + expect(formValue).not.toBeNull(); + expect(formValue).toBe('value_1'); + }); + + it('should retrieve right value from form values if it is present', () => { + visibilityObjTest.rightFormFieldId = 'test_2'; + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).not.toBeNull(); + expect(formTest.values).toEqual(formValues); + expect(rightValue).toBe('value_2'); + }); + + it('should retrieve the value for the left field when it is a form value', () => { + visibilityObjTest.leftFormFieldId = 'FIELD_WITH_CONDITION'; + let leftValue = service.getLeftValue(fakeFormWithField, visibilityObjTest); + + expect(leftValue).not.toBeNull(); + expect(leftValue).toBe('field_with_condition_value'); + }); + + it('should retrieve left value from form values if it is present', () => { + visibilityObjTest.leftFormFieldId = 'test_2'; + let leftValue = service.getLeftValue(formTest, visibilityObjTest); + + expect(leftValue).not.toBeNull(); + expect(leftValue).toBe('value_2'); + }); - let rightValue = service.getRightValue(formTest, visibilityObjTest); + it('should return undefined for a value that is not on variable or form', () => { + let leftValue = service.getLeftValue(fakeFormWithField, visibilityObjTest); - expect(rightValue).not.toBe(null); - expect(formTest.values).toEqual(formValues); - expect(rightValue).toBe('value_2'); - }); + expect(leftValue).toBeUndefined(); + }); - 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'; + it('should evaluate the visibility for the field with single visibility condition between two field values', () => { + visibilityObjTest.leftFormFieldId = 'test_1'; + visibilityObjTest.operator = '=='; + visibilityObjTest.rightFormFieldId = 'test_3'; + let isVisible = service.isFieldVisible(formTest, visibilityObjTest); - let rightValue = service.getRightValue(fakeFormWithField, visibilityObjTest); + expect(isVisible).toBeTruthy(); + }); - expect(rightValue).toBe(null); - }); + it('should evaluate true visibility for the field with single visibility condition between a field and a value', () => { + visibilityObjTest.leftFormFieldId = 'test_1'; + visibilityObjTest.operator = '=='; + visibilityObjTest.rightValue = 'value_1'; + let isVisible = service.isFieldVisible(formTest, visibilityObjTest); - /* - 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(); - }); + expect(isVisible).toBeTruthy(); + }); + + it('should return undefined for a value that is not on variable or form', () => { + visibilityObjTest.rightFormFieldId = 'NO_FIELD_FORM'; + let rightValue = service.getRightValue(fakeFormWithField, visibilityObjTest); + + expect(rightValue).toBeUndefined(); + }); + + it('should evaluate the visibility for the field with single visibility condition between form values', () => { + visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + let isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + + it('should refresh the visibility for a form field object', () => { + visibilityObjTest.leftFormFieldId = 'test_1'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'test_3'; + let fakeFormField: FormFieldModel = new FormFieldModel(formTest, jsonFieldFake); + service.refreshEntityVisibility(fakeFormField); + + expect(fakeFormField.isVisible).toBeFalsy(); + }); + + it('should return true when the visibility condition is not valid', () => { + visibilityObjTest.leftFormFieldId = ''; + visibilityObjTest.leftRestResponseId = ''; + visibilityObjTest.operator = '!='; + let isVisible = service.evaluateVisibility(formTest, visibilityObjTest); + + expect(isVisible).toBeTruthy(); + }); + + it('should return always true when field does not have a visibility condition', () => { + jsonFieldFake.visibilityCondition = null; + let fakeFormField: FormFieldModel = new FormFieldModel(fakeFormWithField, jsonFieldFake); + fakeFormField.isVisible = false; + service.refreshEntityVisibility(fakeFormField); + + expect(fakeFormField.isVisible).toBeTruthy(); + }); + + it('should be able to retrieve the value of a form variable', () => { + let varValue = service.getVariableValue(fakeForm, 'FORM_VARIABLE_TEST', null); + + expect(varValue).not.toBeUndefined(); + expect(varValue).toBe('form_value_test'); + }); + + it('should return undefined for not existing form variable', () => { + let varValue = service.getVariableValue(fakeForm, 'MISTERY_FORM_VARIABLE', null); + + expect(varValue).toBeUndefined(); + }); + + it('should retrieve the value for the left field when it is a form variable', () => { + visibilityObjTest.leftRestResponseId = 'FORM_VARIABLE_TEST'; + let leftValue = service.getLeftValue(fakeForm, visibilityObjTest); + + expect(leftValue).not.toBeNull(); + expect(leftValue).toBe('form_value_test'); + }); + + it('should determine visibility for dropdown on label condition', () => { + let dropdownValue = service.getDropDownName(formTest.values, 'dropdown_LABEL'); + + expect(dropdownValue).not.toBeNull(); + expect(dropdownValue).toBeDefined(); + expect(dropdownValue).toBe('dropdown_label'); + }); + + it('should be able to get the value for a dropdown filtered with Label', () => { + let dropdownValue = service.getValue(formTest.values, 'dropdown_LABEL'); + + expect(dropdownValue).not.toBeNull(); + expect(dropdownValue).toBeDefined(); + expect(dropdownValue).toBe('dropdown_label'); + }); + + it('should be able to get the value for a standard field', () => { + let dropdownValue = service.getValue(formTest.values, 'test_2'); + + expect(dropdownValue).not.toBeNull(); + expect(dropdownValue).toBeDefined(); + expect(dropdownValue).toBe('value_2'); + }); + + it('should get the dropdown label value from a form', () => { + let dropdownValue = service.getFormValue(formTest, 'dropdown_LABEL'); + + expect(dropdownValue).not.toBeNull(); + expect(dropdownValue).toBeDefined(); + expect(dropdownValue).toBe('dropdown_label'); + }); + + it('should get the dropdown id value from a form', () => { + let dropdownValue = service.getFormValue(formTest, 'dropdown'); + + expect(dropdownValue).not.toBeNull(); + expect(dropdownValue).toBeDefined(); + expect(dropdownValue).toBe('dropdown_id'); + }); + + it('should retrieve the value for the right field when it is a dropdown id', () => { + visibilityObjTest.rightFormFieldId = 'dropdown'; + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).toBeDefined(); + expect(rightValue).toBe('dropdown_id'); + }); + + it('should retrieve the value for the right field when it is a dropdown label', () => { + visibilityObjTest.rightFormFieldId = 'dropdown_LABEL'; + let rightValue = service.getRightValue(formTest, visibilityObjTest); + + expect(rightValue).toBeDefined(); + expect(rightValue).toBe('dropdown_label'); + }); + + it('should be able to evaluate condition with a dropdown <label>', () => { + visibilityObjTest.leftFormFieldId = 'test_5'; + visibilityObjTest.operator = '=='; + visibilityObjTest.rightFormFieldId = 'dropdown_LABEL'; + let fakeFormField: FormFieldModel = new FormFieldModel(formTest, jsonFieldFake); + service.refreshEntityVisibility(fakeFormField); + + expect(fakeFormField.isVisible).toBeTruthy(); + }); + + it('should be able to evaluate condition with a dropdown <id>', () => { + visibilityObjTest.leftFormFieldId = 'test_4'; + visibilityObjTest.operator = '=='; + visibilityObjTest.rightFormFieldId = 'dropdown'; + let fakeFormField: FormFieldModel = new FormFieldModel(formTest, jsonFieldFake); + service.refreshEntityVisibility(fakeFormField); + + expect(fakeFormField.isVisible).toBeTruthy(); + }); + + it('should be able to get value from form values', () => { + let res = service.getFormValue(formTest, 'test_1'); + + expect(res).not.toBeNull(); + expect(res).toBeDefined(); + expect(res).toBe('value_1'); + }); + + it('should refresh the visibility for field', () => { + visibilityObjTest.leftFormFieldId = 'FIELD_TEST'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + + let container = <ContainerModel> fakeFormWithField.fields[0]; + let column0 = container.columns[0]; + let column1 = container.columns[1]; + + column0.fields[0].visibilityCondition = visibilityObjTest; + service.refreshVisibility(fakeFormWithField); + + expect(column0.fields[0].isVisible).toBeFalsy(); + expect(column0.fields[1].isVisible).toBeTruthy(); + expect(column0.fields[2].isVisible).toBeTruthy(); + expect(column1.fields[0].isVisible).toBeTruthy(); + }); + + it('should refresh the visibility for tab in forms', () => { + visibilityObjTest.leftFormFieldId = 'FIELD_TEST'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + let tab = new TabModel(fakeFormWithField, {id: 'fake-tab-id', title: 'fake-tab-title', isVisible: true}); + tab.visibilityCondition = visibilityObjTest; + fakeFormWithField.tabs.push(tab); + service.refreshVisibility(fakeFormWithField); + + expect(fakeFormWithField.tabs[0].isVisible).toBeFalsy(); + }); + + it('should refresh the visibility for single tab', () => { + visibilityObjTest.leftFormFieldId = 'FIELD_TEST'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + let tab = new TabModel(fakeFormWithField, {id: 'fake-tab-id', title: 'fake-tab-title', isVisible: true}); + tab.visibilityCondition = visibilityObjTest; + service.refreshEntityVisibility(tab); + + expect(tab.isVisible).toBeFalsy(); + }); + + xit('should refresh the visibility for container in forms', () => { + visibilityObjTest.leftFormFieldId = 'FIELD_TEST'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'LEFT_FORM_FIELD_ID'; + let contModel = new ContainerModel(fakeFormWithField, { + id: 'fake-container-id', + type: FormFieldTypes.GROUP, + name: 'fake-container-name', + isVisible: true, + visibilityCondition: visibilityObjTest + }); + + fakeFormWithField.fields.push(contModel); + service.refreshVisibility(fakeFormWithField); + expect(contModel.isVisible).toBeFalsy(); + }); + + it('should refresh the visibility for single container', () => { + visibilityObjTest.leftFormFieldId = 'FIELD_TEST'; + visibilityObjTest.operator = '!='; + visibilityObjTest.rightFormFieldId = 'RIGHT_FORM_FIELD_ID'; + let contModel = new ContainerModel(fakeFormWithField, { + id: 'fake-container-id', + type: FormFieldTypes.GROUP, + name: 'fake-container-name', + isVisible: true, + visibilityCondition: visibilityObjTest + }); + service.refreshEntityVisibility(contModel.field); + expect(contModel.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 index 56249235b4..1bc357e65a 100644 --- a/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts +++ b/ng2-components/ng2-activiti-form/src/services/widget-visibility.service.ts @@ -18,8 +18,8 @@ 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 { AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { FormModel, FormFieldModel, TabModel } from '../components/widgets/core/index'; import { WidgetVisibilityModel } from '../models/widget-visibility.model'; import { TaskProcessVariableModel } from '../models/task-process-variable.model'; @@ -29,131 +29,153 @@ 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) ) - ) - ); - } + private alfrescoSettingsService: AlfrescoSettingsService, + private authService: AlfrescoAuthenticationService) { } - public refreshVisibilityForField(field: FormFieldModel) { - if ( field.visibilityCondition ) { - field.isVisible = this.getVisiblityForField(field.form, field.visibilityCondition); + public refreshVisibility(form: FormModel) { + if (form && form.tabs && form.tabs.length > 0) { + form.tabs.map(tabModel => this.refreshEntityVisibility(tabModel)); + } + + if (form) { + form.getFormFields().map(field => this.refreshEntityVisibility(field)); } } - public getVisiblityForField(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean { - let isLeftFieldPresent = visibilityObj.leftFormFieldId || visibilityObj.leftRestResponseId; - if ( !isLeftFieldPresent ) { + refreshEntityVisibility(element: FormFieldModel | TabModel) { + element.isVisible = this.evaluateVisibility(element.form, element.visibilityCondition); + } + + evaluateVisibility(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean { + let isLeftFieldPresent = visibilityObj && ( visibilityObj.leftFormFieldId || visibilityObj.leftRestResponseId ); + if (!isLeftFieldPresent || isLeftFieldPresent === 'null') { return true; - }else { - return this.evaluateVisibilityForField(form, visibilityObj); + } else { + return this.isFieldVisible(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 { + isFieldVisible(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.isFieldVisible(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; + getLeftValue(form: FormModel, visibilityObj: WidgetVisibilityModel) { + if (visibilityObj.leftRestResponseId && visibilityObj.leftRestResponseId !== 'null') { + return this.getVariableValue(form, visibilityObj.leftRestResponseId, this.processVarList); + } + return this.getFormValue(form, visibilityObj.leftFormFieldId); + } + + getRightValue(form: FormModel, visibilityObj: WidgetVisibilityModel) { + let valueFound = ''; + if (visibilityObj.rightRestResponseId) { + valueFound = this.getVariableValue(form, visibilityObj.rightRestResponseId, this.processVarList); + } else if (visibilityObj.rightFormFieldId) { + valueFound = this.getFormValue(form, visibilityObj.rightFormFieldId); + } else { + if (moment(visibilityObj.rightValue, 'YYYY-MM-DD', true).isValid()) { + valueFound = visibilityObj.rightValue + 'T00:00:00.000Z'; + } 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; + getFormValue(form: FormModel, field: string) { + let value = this.getValue(form.values, field); + value = value && value.id ? value.id : value; + return value ? value : this.searchForm(form, field); } - 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; + getValue(values: any, fieldName: string) { + return this.getFieldValue(values, fieldName) || + this.getDropDownName(values, fieldName); } - 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; + getFieldValue(valueList: any, fieldName: string) { + return valueList[fieldName]; } - private evaluateLogicalOperation(logicOp, previousValue, newValue): boolean { - switch ( logicOp ) { + getDropDownName(valueList: any, fieldName: string) { + let dropDownFilterByName; + if (fieldName && fieldName.indexOf('_LABEL') > 0) { + dropDownFilterByName = fieldName.substring(0, fieldName.length - 6); + } + return ( dropDownFilterByName && valueList[dropDownFilterByName] ) ? + valueList[dropDownFilterByName].name : dropDownFilterByName; + } + + searchForm(form: FormModel, name: string) { + let res; + form.json.fields.forEach(columns => { + for (let i in columns.fields) { + if (columns.fields.hasOwnProperty(i)) { + res = columns.fields[i].find(field => field.id === name); + if (res) { + return res.value; + } + } + } + }); + return res ? res.value : res; + } + + getVariableValue(form: FormModel, name: string, processVarList: TaskProcessVariableModel[]) { + return this.getFormVariableValue(form, name) || + this.getProcessVariableValue(name, processVarList); + } + + private getFormVariableValue(form: FormModel, name: string) { + if (form.json.variables) { + let formVariable = form.json.variables.find(formVar => formVar.name === name); + return formVariable ? formVariable.value : formVariable; + } + } + + private getProcessVariableValue(name: string, processVarList: TaskProcessVariableModel[]) { + if (this.processVarList) { + let processVariable = this.processVarList.find(variable => variable.id === name); + return processVariable ? processVariable.value : processVariable; + } + } + + evaluateLogicalOperation(logicOp, previousValue, newValue): boolean { + switch (logicOp) { case 'and': return previousValue && newValue; case 'or' : return previousValue || newValue; - case 'and not': + case 'and-not': return previousValue && !newValue; - case 'or not': + case 'or-not': return previousValue || !newValue; default: - console.error( 'NO valid operation!' ); + console.error('NO valid operation! wrong op request : ' + logicOp); break; } } - private evaluateCondition(leftValue, rightValue, operator): boolean { - switch ( operator ) { + evaluateCondition(leftValue, rightValue, operator): boolean { + switch (operator) { case '==': - return leftValue + '' === rightValue; + return leftValue + '' === rightValue + ''; case '<': return leftValue < rightValue; case '!=': - return leftValue + '' !== rightValue; + return leftValue + '' !== rightValue + ''; case '>': return leftValue > rightValue; case '>=': @@ -165,32 +187,26 @@ export class WidgetVisibilityService { case '!empty': return leftValue ? leftValue !== '' : false; default: - console.error( 'NO valid operation!' ); + console.error('NO valid operation!'); break; } - return null; + return; } - getTaskProcessVariableModelsForTask(taskId: string): Observable<TaskProcessVariableModel[]> { + getTaskProcessVariable(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()) + .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' + 'Content-Type': 'application/json', + 'Authorization': this.authService.getTicketBpm() }); } diff --git a/ng2-components/ng2-activiti-form/tsconfig.json b/ng2-components/ng2-activiti-form/tsconfig.json index e14b002fcf..0ed9d674eb 100644 --- a/ng2-components/ng2-activiti-form/tsconfig.json +++ b/ng2-components/ng2-activiti-form/tsconfig.json @@ -1,21 +1,20 @@ { "compilerOptions": { "target": "es5", - "module": "system", + "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, "removeComments": true, "declaration": true, - "outDir": "dist" + "outDir": "dist", + "types": ["core-js", "jasmine"] }, "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 index 0a34e57fec..35099dcec4 100644 --- a/ng2-components/ng2-activiti-form/tslint.json +++ b/ng2-components/ng2-activiti-form/tslint.json @@ -35,7 +35,7 @@ "no-arg": true, "no-bitwise": false, "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, + "no-consecutive-blank-lines": true, "no-console": [ true, "debug", diff --git a/ng2-components/ng2-activiti-form/typings.json b/ng2-components/ng2-activiti-form/typings.json deleted file mode 100644 index d8954c2485..0000000000 --- a/ng2-components/ng2-activiti-form/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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/.npmignore b/ng2-components/ng2-activiti-processlist/.npmignore new file mode 100644 index 0000000000..c5ca623298 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/.npmignore @@ -0,0 +1,15 @@ +npm-debug.log +.idea + +coverage/ +node_modules +typings/ +fonts/ + +/.editorconfig +/.travis.yml +/*.js +/*.json +/*.ts +/*.js.map +/.npmignore diff --git a/ng2-components/ng2-activiti-processlist/README.md b/ng2-components/ng2-activiti-processlist/README.md index 26a3aa8ad6..be0d6f1308 100644 --- a/ng2-components/ng2-activiti-processlist/README.md +++ b/ng2-components/ng2-activiti-processlist/README.md @@ -1,4 +1,4 @@ -# Activiti Proess List Component for Angular 2 +# Activiti Process List Component for Angular 2 <p> <a title='Build Status Travis' href="https://travis-ci.org/Alfresco/alfresco-ng2-components"> <img src='https://travis-ci.org/Alfresco/alfresco-ng2-components.svg?branch=master' alt='travis @@ -31,88 +31,396 @@ </a> </p> +Displays lists of process instances both active and completed, using any defined process filter, and +render details of any chosen instance. ## 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). +necessary configuration [prerequisites](https://github.com/Alfresco/alfresco-ng2-components/blob/master/PREREQUISITES.md). -## About ng-2-activiti-processlist -> Show available processes from the Activiti BPM suite +## Install -## Installation +Follow the 3 steps below: -```bash -npm install ng-2-activiti-processlist --save -``` +1. Npm -## Example + ```sh + npm install ng2-activiti-processlist --save + ``` + +2. Html + + Include these dependencies in your index.html page: + + ```html + + <!-- Moment js --> + <script src="node_modules/moment/min/moment.min.js"></script> + + <!-- Date picker --> + <script src="node_modules/md-date-time-picker/dist/js/mdDateTimePicker.min.js"></script> + <script src="node_modules/md-date-time-picker/dist/js/draggabilly.pkgd.min.js"></script> + <link rel="stylesheet" href="node_modules/md-date-time-picker/dist/css/mdDateTimePicker.css" media="all"> + + <!-- Google Material Design Lite --> + <link rel="stylesheet" href="node_modules/material-design-lite/material.min.css"> + <script src="node_modules/material-design-lite/material.min.js"></script> + <link rel="stylesheet" href="node_modules/material-design-icons/iconfont/material-icons.css"> + + <!-- Polyfill(s) for Safari (pre-10.x) --> + <script src="node_modules/intl/dist/Intl.min.js"></script> + <script src="node_modules/intl/locale-data/jsonp/en.js"></script> + + <!-- Polyfill(s) for older browsers --> + <script src="node_modules/core-js/client/shim.min.js"></script> + <script src="//cdnjs.cloudflare.com/ajax/libs/dom4/1.8.3/dom4.js"></script> + <script src="node_modules/element.scrollintoviewifneeded-polyfill/index.js"></script> + + <!-- Polyfill(s) for dialogs --> + <script src="node_modules/dialog-polyfill/dialog-polyfill.js"></script> + <link rel="stylesheet" type="text/css" href="node_modules/dialog-polyfill/dialog-polyfill.css" /> + <style>._dialog_overlay { position: static !important; } </style> + + <!-- Modules --> + <script src="node_modules/zone.js/dist/zone.js"></script> + <script src="node_modules/reflect-metadata/Reflect.js"></script> + <script src="node_modules/systemjs/dist/system.src.js"></script> + + ``` + +3. SystemJs + + Add the following components to your systemjs.config.js file: + + - ng2-translate + - alfresco-js-api + - ng2-alfresco-core + - ng2-activiti-form + - ng2-alfresco-datatable + - ng2-activiti-tasklist + - ng2-activiti-processlist + + Please refer to the following example file: [systemjs.config.js](demo/systemjs + .config.js) . + +## Basic usage + +### Activiti Process Instance List + +This component renders a list containing all the process instances matched by the filter specified. ```html -<ng-2-activiti-processlist></ng-2-activiti-processlist> +<activiti-process-instance-list [filter]="processFilterModel"></activiti-tasklist> ``` -## Reference +Usage example of this component : -Attribute | Options | Default | Description ---- | --- | --- | --- -`foo` | *string* | `bar` | Lorem ipsum dolor. +**main.ts** +```ts + +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { FilterRepresentationModel } from 'ng2-activiti-tasklist'; +import { CoreModule } from 'ng2-alfresco-core'; +import { ActivitiProcessListModule } from 'ng2-activiti-processlist'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { ObjectDataTableAdapter, DataSorting } from 'ng2-alfresco-datatable'; + +@Component({ + selector: 'alfresco-app-demo', + template: `<activiti-process-instance-list [filter]="filterRepresentationModel" [data]="dataProcesses" + #activitiprocesslist></activiti-process-instance-list>` +}) +class MyDemoApp { + + dataProcesses: ObjectDataTableAdapter; + + filterRepresentationModel: FilterRepresentationModel; + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = 'http://localhost:9999'; + + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + }, + error => { + console.log(error); + }); + + this.dataProcesses = new ObjectDataTableAdapter([], [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'started', title: 'Started', cssClass: 'hidden', sortable: true} + ] + ); + this.dataProcesses.setSorting(new DataSorting('started', 'desc')); + + this.filterRepresentationModel = new FilterRepresentationModel({ + appId: '3003', + filter: { + processDefinitionKey: null, + name: null, + state: 'running', + sort: 'created-desc' + } + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + ActivitiProcessListModule + ], + declarations: [MyDemoApp], + bootstrap: [MyDemoApp] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); -Method | Parameters | Returns | Description ---- | --- | --- | --- -`methodName()` | None. | void | Lorem ipsum dolor. +``` -## Develop command list +#### Options + +| Name | Description | +| --- | --- | +|`filter`| { UserProcessInstanceFilterRepresentationModel } (required) UserProcessInstanceFilterRepresentationModel object that is passed to the process instance list API to filter the returned list. | + +Example: -* To test your component +```json +{ + appId: '3003', + filter:{ + processDefinitionKey: null, + name:null, + state:'running', + sort: 'created-desc' + } +} +``` - ```sh - $ npm run test - ``` - -* To run the test in the browser +| Name | Description | +| --- | --- | +|`schemaColumn`| {any} List of columns to display in the process instances datatable | - ```sh - $ npm run test-browser - ``` +Example: -* To run the test coverage +```json +[ + {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} +] +``` - ```sh - $ npm run coverage - ``` - -* To build the component +#### Events - ```sh - $ npm run build - ``` - -* To build the component and keep watching the changes +- **rowClick**: Emitted when a row in the process list is clicked +- **onSuccess**: Emitted when the list of process instances has been loaded successfully from the server +- **onError**: Emitted when an error is encountered loading the list of process instances from the server - ```sh - $ npm run build:w - ``` +### Process Filters component -* To provide a live demo +Process filters are a collection of criteria used to filter process instances, which may be customized +by users. This component displays a list of available filters and allows the user to select any given +filter as the active filter. - ```sh - $ cd demo - $ npm run start - ``` - -* To clean npm_modules and typings folder +The most common usage is in driving a process instance list in order to allow the user to choose which +process instances are displayed in the list. - ```sh - $ npm run clean - ``` - -## History +```html +<activiti-process-instance-filters appId="1001"></activiti-process-instance-filters> +``` -For detailed changelog, check [Releases](https://github.com/Alfresco/ng-2-activiti-processlist/releases). +#### Options -## Contributors +| Name | Description | +| --- | --- | +| `appId` | Display filters available to the current user for the application with the specified ID | +| `appName` | Display filters available to the current user for the application with the specified name | -[Contributors](https://github.com/Alfresco/ng-2-activiti-processlist/graphs/contributors) \ No newline at end of file +If both `appId` and `appName` are specified then `appName` will take precedence and `appId` will be ignored. + +#### Events + +| Name | Description | +| --- | --- | +| `onSuccess` | Emitted when the list of filters hase been successfully loaded from the server | +| `onError` | Emitted when an error occurs | +| `ilterClick` | Emitted when the user selects a filter from the list | + +### Start Process Button component + +Displays a button which in turn displays a dialog when clicked, allowing the user +to specify some basic details needed to start a new process instance. + +```html +<activiti-start-process-instance></activiti-start-process-instance> +``` + +#### Options + + +| Name | Description | +| --- | --- | +| `appId` | Limit the list of processes which can be started to those contained in the specified app | + +#### Events + +No events are emitted by this component + +### Process Details component + +This component displays detailed information on a specified process instance + +```html +<activiti-process-instance-details processInstanceId="123"></activiti-process-instance-details> +``` + +#### Options + + +| Name | Description | +| --- | --- | +| `processInstanceId` | (required): The numeric ID of the process instance to display | + +#### Events + + +| Name | Description | +| --- | --- | +| `processCancelledEmitter` | Emitted when the current process is cancelled by the user from within the component | +| `taskFormCompletedEmitter` | Emitted when the form associated with an active task is completed from within the component | + +### Process Instance Details Header component + +This is a sub-component of the process details component, which renders some general information about the selected process. + +```html +<activiti-process-instance-header processInstance="localProcessDetails"></activiti-process-instance-details> +``` + +#### Options + + +| Name | Description | +| --- | --- | +| `processInstance` | (required): Full details of the process instance to display information about | + +#### Events + + +| Name | Description | +| --- | --- | +| `processCancelled` | Emitted when the Cancel Process button shown by the component is clicked | + +### Process Instance Tasks component + +Lists both the active and completed tasks associated with a particular process instance + +```html +<activiti-process-instance-tasks processInstanceId="123" showRefreshButton="true"></activiti-process-instance-tasks> +``` + +#### Options + + +| Name | Description | +| --- | --- | +| `processInstanceId` | (required): The numeric ID of the process instance to display tasks for | +| `showRefreshButton` | (default: `true`): Whether to show a refresh button next to the list of tasks to allow this to be updated from the server | + +#### Events + +| Name | Description | +| --- | --- | +| `taskFormCompletedEmitter` | Emitted when the form associated with an active task is completed from within the component | + +### Process Instance Comments component + +Displays comments associated with a particular process instances and allows the user to add new comments + +```html +<activiti-process-instance-comments processInstanceId="123"></activiti-process-instance-comments> +``` + +#### Options + + +| Name | Description | +| --- | --- | +| `processInstanceId` | (required): The numeric ID of the process instance to display comments for | + +#### Events + +No events are emitted by this component + +## 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 +``` + +## Demo + +If you want have a demo of how the component works, please check the demo folder : + +```sh +cd demo +npm install +npm start +``` + +## NPM scripts + +| Command | Description | +| --- | --- | +| npm run build | Build component | +| npm run build:w | Build component and keep watching the changes | +| npm run test | Run unit tests in the console | +| npm run test-browser | Run unit tests in the browser +| npm run coverage | Run unit tests and display code coverage report | + +## License + +[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE) diff --git a/ng2-components/ng2-activiti-processlist/angular-cli.json b/ng2-components/ng2-activiti-processlist/angular-cli.json deleted file mode 100644 index c6a5e72386..0000000000 --- a/ng2-components/ng2-activiti-processlist/angular-cli.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "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/data/processlist-datatable-adapter.ts b/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts index b3e39360f4..521fc7bab1 100644 --- a/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts +++ b/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts @@ -66,7 +66,7 @@ export class ProcessListDataTableAdapter extends ObjectDataTableAdapter implemen let value = row.getValue(col.key); if (col.type === 'date') { - let datePipe = new DatePipe(); + let datePipe = new DatePipe('en-US'); let format = (<ActivitiDataColumn>(col)).format || this.DEFAULT_DATE_FORMAT; try { return datePipe.transform(value, format); diff --git a/ng2-components/ng2-activiti-processlist/demo/index.html b/ng2-components/ng2-activiti-processlist/demo/index.html index 63fd1b2b57..fe525218aa 100644 --- a/ng2-components/ng2-activiti-processlist/demo/index.html +++ b/ng2-components/ng2-activiti-processlist/demo/index.html @@ -1,37 +1,55 @@ <!DOCTYPE html> -<html> - <head> - <meta charset="UTF-8"> - <title>ng2-activiti-processlist Angular 2 - + + + + + Alfresco Angular 2 Activiti Process - Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + - - - - - - - - - - - - - - - -
-
- + + - - diff --git a/ng2-components/ng2-activiti-processlist/demo/package.json b/ng2-components/ng2-activiti-processlist/demo/package.json index fcc6e823e2..a1fdda82a3 100644 --- a/ng2-components/ng2-activiti-processlist/demo/package.json +++ b/ng2-components/ng2-activiti-processlist/demo/package.json @@ -5,13 +5,12 @@ "author": "Will Abson", "main": "index.js", "scripts": { - "clean": "rimraf dist node_modules typings", - "typings": "typings install", - "postinstall": "npm run typings && npm run build", + "clean": "npm install rimraf && rimraf dist node_modules typings dist", + "postinstall": "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", + "build": "npm run tslint && rimraf dist && tsc", + "build:w": "npm run tslint && rimraf dist && tsc -w", "tsc": "tsc", "tsc:w": "tsc -w", "tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts" @@ -24,34 +23,43 @@ "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", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", "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" + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "moment": "2.15.1", + "md-date-time-picker": "^2.2.0", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-activiti-tasklist": "0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-alfresco-datatable": "0.4.0", + "ng2-activiti-processlist": "^0.4.0" }, "devDependencies": { - "concurrently": "^2.0.0", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.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/" + "typescript": "^2.0.3", + "wsrv": "^0.1.5" } } diff --git a/ng2-components/ng2-activiti-processlist/demo/src/main.ts b/ng2-components/ng2-activiti-processlist/demo/src/main.ts index 9d3c4df947..252ab72fcf 100644 --- a/ng2-components/ng2-activiti-processlist/demo/src/main.ts +++ b/ng2-components/ng2-activiti-processlist/demo/src/main.ts @@ -12,90 +12,221 @@ * 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 => { + * limitations under the License. + */ + +import { Input, NgModule, Component, OnInit, ViewChild } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppDefinitionRepresentationModel, ActivitiTaskListModule } from 'ng2-activiti-tasklist'; +import { CoreModule } from 'ng2-alfresco-core'; +import { ActivitiProcessListModule } from 'ng2-activiti-processlist'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { ObjectDataTableAdapter, DataSorting } from 'ng2-alfresco-datatable'; + +@Component({ + selector: 'alfresco-app-demo', + template: ` +
+
+
+

+
+ Authentication failed to ip {{ host }} with user: admin, admin, you can still try to add a valid ticket to perform + operations. +
+
+ +
+ +
+ + + + +
+ +
+ + + +
+
+ +
+
+ + + +
+
+
+
+
+ Process Filters + + +
+
+ Process List + +
+
+ Process Details + +
+
+
+
+
+ +
+
+` +}) +class MyDemoApp implements OnInit { + + authenticated: boolean; + + host: string = 'http://localhost:9999'; + + ticket: string; + + @ViewChild('tabmain') + tabMain: any; + + @ViewChild('tabheader') + tabHeader: any; + + @ViewChild('activitiprocessfilter') + activitiprocessfilter: any; + + @ViewChild('activitiprocesslist') + activitiprocesslist: any; + + @ViewChild('activitiprocessdetails') + activitiprocessdetails: any; + + @Input() + appId: number; + + processFilter: any; + + currentProcessInstanceId: string; + + dataProcesses: ObjectDataTableAdapter; + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = this.host; + settingsService.setProviders('BPM'); + + if (this.authService.getTicketBpm()) { + this.ticket = this.authService.getTicketBpm(); + } + + this.dataProcesses = new ObjectDataTableAdapter( + [], + [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'started', title: 'Started', cssClass: 'hidden', sortable: true} + ] + ); + this.dataProcesses.setSorting(new DataSorting('started', 'desc')); + } + + public updateTicket(): void { + localStorage.setItem('ticket-BPM', this.ticket); + } + + public updateHost(): void { + this.settingsService.bpmHost = this.host; + this.login(); + } + + public ngOnInit(): void { + this.login(); + } + + login() { + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + this.ticket = this.authService.getTicketBpm(); + this.authenticated = true; + }, + error => { console.log(error); this.authenticated = false; }); } + + onAppClick(app: AppDefinitionRepresentationModel) { + this.appId = app.id; + + this.processFilter = null; + this.currentProcessInstanceId = null; + + this.changeTab('apps', 'processes'); + } + + onProcessFilterClick(event: any) { + this.processFilter = event; + } + + onSuccessProcessFilterList(event: any) { + this.processFilter = this.activitiprocessfilter.getCurrentFilter(); + } + + onSuccessProcessList(event: any) { + this.currentProcessInstanceId = this.activitiprocesslist.getCurrentProcessId(); + } + + onProcessRowClick(processInstanceId) { + this.currentProcessInstanceId = processInstanceId; + } + + processCancelled(data: any) { + this.currentProcessInstanceId = null; + this.activitiprocesslist.reload(); + } + + changeTab(origin: string, destination: string) { + this.tabMain.nativeElement.children[origin].classList.remove('is-active'); + this.tabMain.nativeElement.children[destination].classList.add('is-active'); + + this.tabHeader.nativeElement.children[`${origin}-header`].classList.remove('is-active'); + this.tabHeader.nativeElement.children[`${destination}-header`].classList.add('is-active'); + } + } -bootstrap(MyDemoApp, [ - ALFRESCO_CORE_PROVIDERS -]); +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + ActivitiProcessListModule, + ActivitiTaskListModule.forRoot() + ], + declarations: [MyDemoApp], + bootstrap: [MyDemoApp] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/ng2-components/ng2-activiti-processlist/demo/systemjs.config.js b/ng2-components/ng2-activiti-processlist/demo/systemjs.config.js index a09961cab3..f1fe243137 100644 --- a/ng2-components/ng2-activiti-processlist/demo/systemjs.config.js +++ b/ng2-components/ng2-activiti-processlist/demo/systemjs.config.js @@ -1,71 +1,52 @@ /** - * @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. + * 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', - '@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); + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'npm:ng2-alfresco-datatable/dist', + 'ng2-activiti-form': 'npm:ng2-activiti-form/dist', + 'ng2-activiti-tasklist': 'npm:ng2-activiti-tasklist/dist', + 'ng2-activiti-processlist': 'npm:ng2-activiti-processlist/dist' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ng2-translate': { defaultExtension: 'js' }, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-datatable': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-form': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-tasklist': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-processlist': { main: './index.js', defaultExtension: 'js'} + } + }); })(this); diff --git a/ng2-components/ng2-activiti-processlist/demo/tsconfig.json b/ng2-components/ng2-activiti-processlist/demo/tsconfig.json index 772c3a7e75..c586e1848e 100644 --- a/ng2-components/ng2-activiti-processlist/demo/tsconfig.json +++ b/ng2-components/ng2-activiti-processlist/demo/tsconfig.json @@ -1,19 +1,26 @@ { "compilerOptions": { "target": "es5", - "module": "system", + "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, "removeComments": true, "declaration": true, - "outDir": "dist" + "noLib": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": false, + "noImplicitReturns": false, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "outDir": "dist", + "types": ["core-js", "jasmine", "node"] }, "exclude": [ - "dist", + "demo", "node_modules", - "typings/main", - "typings/main.d.ts" + "dist" ] } diff --git a/ng2-components/ng2-activiti-processlist/demo/tslint.json b/ng2-components/ng2-activiti-processlist/demo/tslint.json index 8c48e76469..55c0f8a666 100644 --- a/ng2-components/ng2-activiti-processlist/demo/tslint.json +++ b/ng2-components/ng2-activiti-processlist/demo/tslint.json @@ -38,7 +38,7 @@ "no-arg": true, "no-bitwise": true, "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, + "no-consecutive-blank-lines": true, "no-console": [ true, "debug", diff --git a/ng2-components/ng2-activiti-processlist/demo/typings.json b/ng2-components/ng2-activiti-processlist/demo/typings.json deleted file mode 100644 index 7e0e18568d..0000000000 --- a/ng2-components/ng2-activiti-processlist/demo/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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 index 34bd934992..97e19efdaa 100644 --- a/ng2-components/ng2-activiti-processlist/index.ts +++ b/ng2-components/ng2-activiti-processlist/index.ts @@ -15,6 +15,12 @@ * limitations under the License. */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CoreModule } from 'ng2-alfresco-core'; +import { DataTableModule } from 'ng2-alfresco-datatable'; +import { ActivitiFormModule } from 'ng2-activiti-form'; +import { ActivitiTaskListModule } from 'ng2-activiti-tasklist'; + 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'; @@ -44,3 +50,31 @@ export const ACTIVITI_PROCESSLIST_DIRECTIVES: [any] = [ export const ACTIVITI_PROCESSLIST_PROVIDERS: [any] = [ ActivitiProcessService ]; + +@NgModule({ + imports: [ + CoreModule, + DataTableModule, + ActivitiFormModule, + ActivitiTaskListModule + ], + declarations: [ + ...ACTIVITI_PROCESSLIST_DIRECTIVES + ], + providers: [ + ...ACTIVITI_PROCESSLIST_PROVIDERS + ], + exports: [ + ...ACTIVITI_PROCESSLIST_DIRECTIVES + ] +}) +export class ActivitiProcessListModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: ActivitiProcessListModule, + providers: [ + ...ACTIVITI_PROCESSLIST_PROVIDERS + ] + }; + } +} diff --git a/ng2-components/ng2-activiti-processlist/karma-test-shim.js b/ng2-components/ng2-activiti-processlist/karma-test-shim.js index 8c7ba100a2..b931cf3867 100644 --- a/ng2-components/ng2-activiti-processlist/karma-test-shim.js +++ b/ng2-components/ng2-activiti-processlist/karma-test-shim.js @@ -5,104 +5,106 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; __karma__.loaded = function() {}; +var builtPath = '/base/dist/'; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +var paths = { + // paths serve as alias + 'npm:': 'base/node_modules/' +}; + 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' + 'app': 'base/dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // testing + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-activiti-form': 'npm:ng2-activiti-form/dist', + 'ng2-activiti-tasklist': 'npm:ng2-activiti-tasklist/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'npm:ng2-alfresco-datatable/dist' }; 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' } -}; + 'app': { main: 'main.js', defaultExtension: 'js' }, + 'rxjs': { 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, {}) + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-activiti-form': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-tasklist': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-datatable': { main: './index.js', defaultExtension: 'js'} }; var config = { - map: map, - packages: packages + paths: paths, + 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 () { +System.import('@angular/core/testing') + .then(initTestBed) + .then(initTesting); + +function initTestBed(){ return Promise.all([ - System.import('@angular/core/testing'), - System.import('@angular/platform-browser-dynamic/testing') + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') ]) - }) - .then(function (providers) { - var testing = providers[0]; - var testingBrowser = providers[1]; + .then(function (providers) { + var coreTesting = providers[0]; + var browserTesting = 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; + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); + }) } -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); - }); +// Import all spec files and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { + return System.import(moduleName); + }) + ) + .then(__karma__.start, __karma__.error); } diff --git a/ng2-components/ng2-activiti-processlist/karma.conf.js b/ng2-components/ng2-activiti-processlist/karma.conf.js index 1a3d76b9db..3eb9845474 100644 --- a/ng2-components/ng2-activiti-processlist/karma.conf.js +++ b/ng2-components/ng2-activiti-processlist/karma.conf.js @@ -1,54 +1,68 @@ 'use strict'; module.exports = function (config) { - config.set({ - + 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/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}, + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', - {pattern: 'karma-test-shim.js', included: true, watched: true}, + // Polyfills + 'node_modules/core-js/client/shim.js', + 'node_modules/reflect-metadata/Reflect.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, + { pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, + + // Paths loaded via module imports: + // Angular itself + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, + + 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, + + 'karma-test-shim.js', // 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}, + // ng2-components + { pattern: 'node_modules/ng2-alfresco-core/dist/**/*.*', included: false, served: true, watched: false }, + { pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.*', 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-form/dist/**/*.*', included: false, served: true, watched: false }, + // 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/' }, - // list of files to exclude - exclude: [ - 'node_modules/**/*spec.js' - ], - port: 9876, // level of logging @@ -61,25 +75,34 @@ module.exports = function (config) { 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'], + 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'] + 'dist/**/!(*spec|index|*mock|*model).js': 'coverage' }, coverageReporter: { + includeAllSources: true, dir: 'coverage/', subdir: 'report', reporters: [ @@ -89,5 +112,11 @@ module.exports = function (config) { {type: 'lcov'} ] } - }) + }; + + if (process.env.TRAVIS) { + configuration.browsers = ['Chrome_travis_ci']; + } + + config.set(configuration) }; diff --git a/ng2-components/ng2-activiti-processlist/package.json b/ng2-components/ng2-activiti-processlist/package.json index f918590830..cd19bc7a4e 100644 --- a/ng2-components/ng2-activiti-processlist/package.json +++ b/ng2-components/ng2-activiti-processlist/package.json @@ -1,13 +1,10 @@ { "name": "ng2-activiti-processlist", "description": "Show active processes from the Activiti BPM suite", - "version": "0.3.0", + "version": "0.4.0", "author": "Alfresco Software, Ltd.", "scripts": { - "postinstall": "typings install", - "clean": "rimraf dist node_modules typings", - "typings": "typings install", - "server": "wsrv -o -p 9875", + "clean": "npm install rimraf && rimraf dist node_modules typings", "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\"", @@ -20,9 +17,9 @@ "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", + "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report", "prepublish": "npm run build", - "travis": "npm link ng2-alfresco-core ng2-alfresco-datatable" + "travis": "npm link ng2-alfresco-core ng2-alfresco-datatable ng2-activiti-form ng2-activiti-tasklist" }, "main": "./dist/index.js", "typings": "./dist/index.d.ts", @@ -47,38 +44,45 @@ "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", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", "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" + "rxjs": "5.0.0-beta.12", + "systemjs": "0.19.27", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "moment": "2.15.1", + "md-date-time-picker": "^2.2.0", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-activiti-tasklist": "0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-alfresco-datatable": "0.4.0" }, "devDependencies": { - "angular-cli": "1.0.0-beta.9", - "concurrently": "^2.1.0", - "coveralls": "^2.11.9", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", "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", @@ -88,18 +92,12 @@ "rimraf": "2.5.2", "traceur": "^0.0.91", "tslint": "^3.8.1", - "typescript": "^1.8.10", - "typings": "^1.0.4" + "typescript": "^2.0.3", + "wsrv": "^0.1.5" }, "license-check-config": { "src": [ - "**/*.js", - "**/*.ts", - "!/**/coverage/**/*", - "!/**/demo/**/*", - "!/**/node_modules/**/*", - "!/**/typings/**/*", - "!*.js" + "./dist/**/*.js" ], "path": "assets/license_header.txt", "blocking": false, diff --git a/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.service.mock.ts b/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.service.mock.ts new file mode 100644 index 0000000000..9519380532 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.service.mock.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 { FilterRepresentationModel } from 'ng2-activiti-tasklist'; + +export var fakeFilters = { + size: 0, total: 0, start: 0, + data: [new FilterRepresentationModel({ + 'name': 'Running', + 'appId': '22', + 'recent': true, + 'icon': 'glyphicon-random', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'running'} + })] +}; + +export var fakeEmptyFilters = { + size: 0, total: 0, start: 0, + data: [] +}; + +export var fakeApi = { + activiti: { + userFiltersApi: { + getUserProcessInstanceFilters: (filterOpts) => Promise.resolve({}), + createUserProcessInstanceFilter: (filter: FilterRepresentationModel) => Promise.resolve(filter) + } + } +}; + +export var fakeError = { + message: null, + messageKey: 'GENERAL.ERROR.FORBIDDEN' +}; 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 index 023104944a..539b2af021 100644 --- a/ng2-components/ng2-activiti-processlist/src/assets/translation.service.mock.ts +++ b/ng2-components/ng2-activiti-processlist/src/assets/translation.service.mock.ts @@ -16,22 +16,13 @@ */ 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.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts index 95f1219c92..29683e609a 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts @@ -15,29 +15,26 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, Input, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { ActivitiProcessService } from './../services/activiti-process.service'; -import { Comment } from '../models/comment.model'; +import { Comment } 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-comments', - moduleId: __moduleName, + selector: 'activiti-process-instance-comments', + moduleId: module.id, templateUrl: './activiti-comments.component.html', styleUrls: ['./activiti-comments.component.css'], - providers: [ActivitiProcessService], - pipes: [ AlfrescoPipeTranslate ] - + providers: [ActivitiProcessService] }) -export class ActivitiComments implements OnInit { +export class ActivitiComments implements OnInit, OnChanges { @Input() - processId: string; + processInstanceId: string; @ViewChild('dialog') dialog: any; @@ -54,8 +51,7 @@ export class ActivitiComments implements OnInit { * @param auth * @param translate */ - constructor(private auth: AlfrescoAuthenticationService, - private translate: AlfrescoTranslationService, + constructor(private translate: AlfrescoTranslationService, private activitiProcess: ActivitiProcessService) { if (translate) { @@ -70,16 +66,20 @@ export class ActivitiComments implements OnInit { this.comment$.subscribe((comment: Comment) => { this.comments.push(comment); }); + } - if (this.processId) { - this.load(this.processId); + ngOnChanges(changes: SimpleChanges) { + let processInstanceId = changes['processInstanceId']; + if (processInstanceId && processInstanceId.currentValue) { + this.getProcessComments(processInstanceId.currentValue); + return; } } - public load(taskId: string) { + public getProcessComments(processInstanceId: string) { this.comments = []; - if (this.processId) { - this.activitiProcess.getProcessInstanceComments(this.processId).subscribe( + if (processInstanceId) { + this.activitiProcess.getProcessInstanceComments(processInstanceId).subscribe( (res: Comment[]) => { res.forEach((comment) => { this.commentObserver.next(comment); @@ -101,7 +101,7 @@ export class ActivitiComments implements OnInit { } public add() { - this.activitiProcess.addProcessInstanceComment(this.processId, this.message).subscribe( + this.activitiProcess.addProcessInstanceComment(this.processInstanceId, this.message).subscribe( (res: Comment) => { this.comments.push(res); this.message = ''; 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 index 6b7e0a7a66..073fb82bbd 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.css +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.css @@ -1,3 +1,19 @@ .mdl-list__item { cursor: pointer; } + +.activiti-filters__entry { + cursor: pointer; +} + +.activiti-filters__entry-icon { + margin-right: 12px !important; +} + +.activiti-filters__entry.active { + color: rgb(68,138,255); +} + +.activiti-filters__entry.active .activiti-filters__entry-icon { + color: rgb(68,138,255); +} \ No newline at end of file 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 index 580798c6a3..84ac999ab6 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.html +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.html @@ -1,8 +1,9 @@
{{ 'DETAILS.LABELS.STARTED' | translate }}: - {{getStartedDate() | date:'medium'}} + {{getFormatDate(processInstance.started, '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 index 9771c3624e..292d8a08be 100644 --- 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 @@ -16,20 +16,18 @@ */ import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { ProcessInstance } from '../models/process-instance'; import { ActivitiProcessService } from './../services/activiti-process.service'; +import { DatePipe } from '@angular/common'; declare let componentHandler: any; -declare let __moduleName: string; @Component({ selector: 'activiti-process-instance-header', - moduleId: __moduleName, + moduleId: module.id, templateUrl: './activiti-process-instance-header.component.html', - styleUrls: ['./activiti-process-instance-header.component.css'], - pipes: [ AlfrescoPipeTranslate ] - + styleUrls: ['./activiti-process-instance-header.component.css'] }) export class ActivitiProcessInstanceHeader { @@ -37,16 +35,12 @@ export class ActivitiProcessInstanceHeader { processInstance: ProcessInstance; @Output() - processCancelled = new EventEmitter(); + processCancelled: EventEmitter = new EventEmitter(); - /** - * Constructor - * @param auth - * @param translate - * @param activitiProcess - */ - constructor(private auth: AlfrescoAuthenticationService, - private translate: AlfrescoTranslationService, + @Output() + onError: EventEmitter = new EventEmitter(); + + constructor(private translate: AlfrescoTranslationService, private activitiProcess: ActivitiProcessService) { if (translate) { @@ -63,11 +57,22 @@ export class ActivitiProcessInstanceHeader { return ''; } - getStartedDate() { - return this.processInstance ? new Date(this.processInstance.started) : null; + getFormatDate(value, format: string) { + let datePipe = new DatePipe('en-US'); + try { + return datePipe.transform(value, format); + } catch (err) { + console.error(`ProcessListInstanceHeader: error parsing date ${value} to format ${format}`); + } } cancelProcess() { - this.processCancelled.emit(this.activitiProcess.cancelProcess(this.processInstance.id)); + this.activitiProcess.cancelProcess(this.processInstance.id).subscribe( + (res) => { + this.processCancelled.emit(res); + }, (err) => { + console.error(err); + this.onError.emit(err); + }); } } 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 index 0e4e014aa9..00b896928b 100644 --- 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 @@ -5,6 +5,8 @@
+ + {{ 'DETAILS.LABELS.TASKS_ACTIVE'|translate }} @@ -14,15 +16,38 @@ assignment {{task.name}} - {{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: getUserFullName(task.assignee), created: task.created | date:'mediumDate' } }} + {{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: getUserFullName(task.assignee), created: getFormatDate(task.created, 'mediumDate') } }}
+ + +
{{ 'DETAILS.TASKS.NO_ACTIVE' | translate }}
+
+ {{ 'DETAILS.LABELS.START_FORM'|translate }} + + + + +
+ + {{ 'DETAILS.LABELS.TASKS_COMPLETED'|translate }} @@ -32,11 +57,13 @@ assignment {{task.name}} - {{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: getUserFullName(task.assignee), created: task.created | date:'mediumDate' } }} + {{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: + getUserFullName(task.assignee), created: getFormatDate(task.created, 'mediumDate') } }}
+
{{ 'DETAILS.TASKS.NO_COMPLETED' | translate }}
@@ -47,6 +74,18 @@
- + +
+ + + +

{{ 'DETAILS.LABELS.START_FORM'|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 index 5b2cfae098..ad9f8f3592 100644 --- 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 @@ -16,30 +16,26 @@ */ import { Component, Input, OnInit, ViewChild, Output, EventEmitter } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { AlfrescoTranslationService } 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'; +import { TaskDetailsModel } from 'ng2-activiti-tasklist'; +import { Observable, Observer } from 'rxjs/Rx'; +import { DatePipe } from '@angular/common'; +import { ProcessInstance } from '../models/process-instance'; declare let componentHandler: any; -declare let __moduleName: string; +declare let dialogPolyfill: any; @Component({ selector: 'activiti-process-instance-tasks', - moduleId: __moduleName, + moduleId: module.id, templateUrl: './activiti-process-instance-tasks.component.html', - styleUrls: ['./activiti-process-instance-tasks.component.css'], - providers: [ActivitiProcessService], - directives: [ ALFRESCO_TASKLIST_DIRECTIVES ], - pipes: [ AlfrescoPipeTranslate ] - + styleUrls: ['./activiti-process-instance-tasks.component.css'] }) export class ActivitiProcessInstanceTasks implements OnInit { @Input() - processId: string; + processInstanceDetails: ProcessInstance; @Input() showRefreshButton: boolean = true; @@ -60,29 +56,25 @@ export class ActivitiProcessInstanceTasks implements OnInit { selectedTaskId: string; + processId: string; + @ViewChild('dialog') dialog: any; + @ViewChild('startDialog') + startDialog: any; + @ViewChild('taskdetails') taskdetails: any; - /** - * Constructor - * @param auth - * @param translate - * @param activitiProcess - */ - constructor(private auth: AlfrescoAuthenticationService, - private translate: AlfrescoTranslationService, + constructor(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(); - + this.task$ = new Observable(observer => this.taskObserver = observer).share(); + this.completedTask$ = new Observable(observer => this.completedTaskObserver = observer).share(); } ngOnInit() { @@ -93,8 +85,8 @@ export class ActivitiProcessInstanceTasks implements OnInit { this.completedTasks.push(task); }); - if (this.processId) { - this.load(this.processId); + if (this.processInstanceDetails && this.processInstanceDetails.id) { + this.load(this.processInstanceDetails.id); } } @@ -148,20 +140,49 @@ export class ActivitiProcessInstanceTasks implements OnInit { return ''; } + getFormatDate(value, format: string) { + let datePipe = new DatePipe('en-US'); + try { + return datePipe.transform(value, format); + } catch (err) { + console.error(`ProcessListInstanceTask: error parsing date ${value} to format ${format}`); + } + } + public clickTask($event: any, task: TaskDetailsModel) { this.selectedTaskId = task.id; this.taskdetails.loadDetails(task.id); this.showDialog(); } + public clickStartTask() { + this.processId = this.processInstanceDetails.id; + this.showStartDialog(); + } + + public showStartDialog() { + if (!this.startDialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.startDialog.nativeElement); + } + + if (this.startDialog) { + this.startDialog.nativeElement.showModal(); + } + } + public showDialog() { + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); + } if (this.dialog) { this.dialog.nativeElement.showModal(); } } - public cancelDialog() { - this.closeDialog(); + public closeSartDialog() { + if (this.startDialog) { + this.startDialog.nativeElement.close(); + } } private closeDialog() { @@ -172,11 +193,11 @@ export class ActivitiProcessInstanceTasks implements OnInit { public taskFormCompleted() { this.closeDialog(); - this.load(this.processId); - this.taskFormCompletedEmitter.emit(this.processId); + this.load(this.processInstanceDetails.id); + this.taskFormCompletedEmitter.emit(this.processInstanceDetails.id); } public onRefreshClicked() { - this.load(this.processId); + this.load(this.processInstanceDetails.id); } } 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 index 6f22951373..5d7836cf22 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts @@ -15,45 +15,31 @@ * 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 { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { ObjectDataTableAdapter, DataRowEvent, DataTableAdapter, ObjectDataRow } from 'ng2-alfresco-datatable'; +import { TaskQueryRequestRepresentationModel, FilterRepresentationModel } from 'ng2-activiti-tasklist'; import { ActivitiProcessService } from '../services/activiti-process.service'; -import { FilterModel } from '../models/filter.model'; - -declare let __moduleName: string; @Component({ - moduleId: __moduleName, + moduleId: module.id, 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 ] + templateUrl: './activiti-processlist.component.html' }) -export class ActivitiProcessInstanceListComponent implements OnInit { - - errorMessage: string; - data: ObjectDataTableAdapter; - currentProcessInstanceId: string; +export class ActivitiProcessInstanceListComponent implements OnInit, OnChanges { @Input() - filter: FilterModel; + filter: FilterRepresentationModel; @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} - ]; + data: DataTableAdapter; @Output() rowClick: EventEmitter = new EventEmitter(); @@ -64,27 +50,56 @@ export class ActivitiProcessInstanceListComponent implements OnInit { @Output() onError: EventEmitter = new EventEmitter(); - constructor (private processService: ActivitiProcessService, private translate: AlfrescoTranslationService) { + errorMessage: string; + currentProcessInstanceId: string; + + private defaultSchemaColumn: 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} + ]; + + 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); + if (!this.data) { + this.data = this.initDefaultSchemaColumns(); + } + this.reload(); + } + + ngOnChanges(changes: SimpleChanges) { + let filter = changes['filter']; + if (filter && filter.currentValue) { + let requestNode = this.convertProcessInstanceToTaskQuery(filter.currentValue); + this.load(requestNode); + return; } } - load(filter: FilterModel) { - this.processService.getProcessInstances(filter) + /** + * Return an initDefaultSchemaColumns instance with the default Schema Column + * @returns {ObjectDataTableAdapter} + */ + initDefaultSchemaColumns(): ObjectDataTableAdapter { + return new ObjectDataTableAdapter( + [], + this.defaultSchemaColumn + ); + } + + load(requestNode: TaskQueryRequestRepresentationModel) { + this.processService.getProcessInstances(requestNode) .subscribe( (processInstances) => { - this.renderProcessInstances(processInstances); + let processRow = this.createDataRow(processInstances); + this.renderProcessInstances(processRow); + this.selectFirstProcess(); this.onSuccess.emit(processInstances); }, error => { @@ -93,8 +108,21 @@ export class ActivitiProcessInstanceListComponent implements OnInit { }); } - reload() { - this.load(this.filter); + /** + * Create an array of ObjectDataRow + * @param processes + * @returns {ObjectDataRow[]} + */ + private createDataRow(processes: any[]): ObjectDataRow[] { + let processRows: ObjectDataRow[] = []; + processes.forEach((row) => { + processRows.push(new ObjectDataRow({ + id: row.id, + name: row.name, + started: row.started + })); + }); + return processRows; } /** @@ -104,10 +132,26 @@ export class ActivitiProcessInstanceListComponent implements OnInit { */ private renderProcessInstances(processInstances: any[]) { processInstances = this.optimizeProcessNames(processInstances); - this.data = new ObjectDataTableAdapter( - processInstances, - this.schemaColumn - ); + this.data.setRows(processInstances); + } + + /** + * Select the first process of a process list if present + */ + private selectFirstProcess() { + if (!this.isListEmpty()) { + this.currentProcessInstanceId = this.data.getRows()[0].getValue('id'); + } else { + this.currentProcessInstanceId = null; + } + } + + /** + * Return the current process + * @returns {string} + */ + getCurrentProcessId(): string { + return this.currentProcessInstanceId; } /** @@ -136,12 +180,30 @@ export class ActivitiProcessInstanceListComponent implements OnInit { */ 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) + '...'; + t.obj.name = t.obj.name || 'No name'; + if (t.obj.name.length > 50) { + t.obj.name = t.obj.name.substring(0, 50) + '...'; } return t; }); return tasks; } + + public reload() { + if (this.filter) { + let requestNode = this.convertProcessInstanceToTaskQuery(this.filter); + this.load(requestNode); + } + } + + private convertProcessInstanceToTaskQuery(processFilter: FilterRepresentationModel) { + let requestNode = { + appDefinitionId: processFilter.appId, + processDefinitionKey: processFilter.filter.processDefinitionKey, + text: processFilter.filter.name, + state: processFilter.filter.state, + sort: processFilter.filter.sort + }; + return new TaskQueryRequestRepresentationModel(requestNode); + } } 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 index 7e9e64291f..24a29cc268 100644 --- 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 @@ -9,3 +9,21 @@ .material-icons:hover { color: rgb(255, 152, 0); } + +.mdl-dialog { + width: 400px; +} + +.mdl-textfield.alf-mdl-selectfield label { + color: rgba(0,0,0,.54); + font-size: 12px; + top: 4px; +} + +.mdl-dialog { + width: -moz-fit-content; + width: -webkit-fit-content; + width: -ms-fit-content; + width: -o-fit-content; + width: fit-content; +} 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 index e5ca33e2be..c506e6f7f9 100644 --- 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 @@ -3,21 +3,30 @@

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

-
- +
+
- +
+ +
- +
diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.spec.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.spec.ts new file mode 100644 index 0000000000..2e9ddef461 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-start-process.component.spec.ts @@ -0,0 +1,158 @@ +/*! + * @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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { ActivitiFormModule } from 'ng2-activiti-form'; +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; +import { FormService } from 'ng2-activiti-form'; +import { TranslationMock } from './../assets/translation.service.mock'; +import { ActivitiStartProcessButton } from './activiti-start-process.component'; +import { ActivitiProcessService } from '../services/activiti-process.service'; + +describe('ActivitiStartProcessButton', () => { + + let componentHandler: any; + let component: ActivitiStartProcessButton; + let fixture: ComponentFixture; + let processService: ActivitiProcessService; + let formService: FormService; + let getDefinitionsSpy: jasmine.Spy; + let startProcessSpy: jasmine.Spy; + let debugElement: DebugElement; + + let newProcess = { + id: '32323', + name: 'Process' + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ CoreModule, ActivitiFormModule ], + declarations: [ + ActivitiStartProcessButton + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiProcessService, + FormService + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiStartProcessButton); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + processService = fixture.debugElement.injector.get(ActivitiProcessService); + formService = fixture.debugElement.injector.get(FormService); + + getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(Observable.of([{ + id: 'my:process1', + name: 'My Process 1' + }, { + id: 'my:process2', + name: 'My Process 2' + }])); + startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(Observable.of(newProcess)); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should display the correct number of processes in the select list', () => { + fixture.detectChanges(); + let selectElement = debugElement.query(By.css('select')); + expect(selectElement.children.length).toBe(3); + }); + + it('should display the correct process def details', (done) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + let optionEl: HTMLOptionElement = debugElement.queryAll(By.css('select option'))[1].nativeElement; + expect(optionEl.value).toBe('my:process1'); + expect(optionEl.textContent.trim()).toBe('My Process 1'); + done(); + }); + }); + + it('should call service to start process if required fields provided', (done) => { + component.name = 'My new process'; + component.showDialog(); + fixture.detectChanges(); + component.onChange('my:process1'); + component.startProcess(); + fixture.whenStable().then(() => { + expect(startProcessSpy).toHaveBeenCalled(); + done(); + }); + }); + + it('should avoid calling service to start process if required fields NOT provided', (done) => { + component.showDialog(); + fixture.detectChanges(); + component.startProcess(); + fixture.whenStable().then(() => { + expect(startProcessSpy).not.toHaveBeenCalled(); + done(); + }); + }); + + it('should call service to start process with the correct parameters', (done) => { + component.name = 'My new process'; + component.showDialog(); + fixture.detectChanges(); + component.onChange('my:process1'); + component.startProcess(); + fixture.whenStable().then(() => { + expect(startProcessSpy).toHaveBeenCalledWith('my:process1', 'My new process', undefined); + done(); + }); + }); + + it('should output start event when process started successfully', (done) => { + let emitSpy = spyOn(component.start, 'emit'); + component.name = 'My new process'; + component.showDialog(); + fixture.detectChanges(); + component.onChange('my:process1'); + component.startProcess(); + fixture.whenStable().then(() => { + expect(emitSpy).toHaveBeenCalledWith(newProcess); + done(); + }); + }); + + it('should indicate start form is missing when process does not have a start form', (done) => { + component.name = 'My new process'; + component.showDialog(); + fixture.detectChanges(); + component.onChange('my:process1'); + component.startProcess(); + fixture.whenStable().then(() => { + expect(component.isStartFormMissingOrValid()).toBe(true); + done(); + }); + }); + +}); 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 index d51de06c9e..e1136e5418 100644 --- 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 @@ -15,43 +15,41 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, EventEmitter, Input, Output, OnInit, ViewChild, DebugElement, OnChanges, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { ActivitiStartForm } from 'ng2-activiti-form'; import { ActivitiProcessService } from './../services/activiti-process.service'; declare let componentHandler: any; -declare let __moduleName: string; +declare let dialogPolyfill: any; @Component({ - selector: 'activiti-start-process', - moduleId: __moduleName, + selector: 'activiti-start-process-instance', + moduleId: module.id, templateUrl: './activiti-start-process.component.html', - styleUrls: ['./activiti-start-process.component.css'], - providers: [ActivitiProcessService], - pipes: [ AlfrescoPipeTranslate ] - + styleUrls: ['./activiti-start-process.component.css'] }) -export class ActivitiStartProcessButton implements OnInit { +export class ActivitiStartProcessButton implements OnInit, OnChanges { @Input() appId: string; + @Output() + start: EventEmitter = new EventEmitter(); + @ViewChild('dialog') - dialog: any; + dialog: DebugElement; + + @ViewChild('startForm') + startForm: ActivitiStartForm; processDefinitions: any[] = []; name: string; - processDefinition: string; - /** - * Constructor - * @param auth - * @param translate - * @param activitiProcess - */ - constructor(private auth: AlfrescoAuthenticationService, - private translate: AlfrescoTranslationService, + currentProcessDef: any; + + constructor(private translate: AlfrescoTranslationService, private activitiProcess: ActivitiProcessService) { if (translate) { @@ -63,7 +61,16 @@ export class ActivitiStartProcessButton implements OnInit { this.load(this.appId); } + ngOnChanges(changes: SimpleChanges) { + let appId = changes['appId']; + if (appId && (appId.currentValue || appId.currentValue === null)) { + this.load(appId.currentValue); + return; + } + } + public load(appId: string) { + this.reset(); this.activitiProcess.getProcessDefinitions(this.appId).subscribe( (res: any[]) => { this.processDefinitions = res; @@ -75,15 +82,19 @@ export class ActivitiStartProcessButton implements OnInit { } public showDialog() { - if (this.dialog) { - this.dialog.nativeElement.showModal(); + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); } + this.dialog.nativeElement.showModal(); } public startProcess() { - if (this.processDefinition && this.name) { - this.activitiProcess.startProcess(this.processDefinition, this.name).subscribe( + if (this.currentProcessDef.id && this.name) { + let formValues = this.startForm ? this.startForm.form.values : undefined; + this.activitiProcess.startProcess(this.currentProcessDef.id, this.name, formValues).subscribe( (res: any) => { + this.name = ''; + this.start.emit(res); this.cancel(); }, (err) => { @@ -94,8 +105,30 @@ export class ActivitiStartProcessButton implements OnInit { } public cancel() { - if (this.dialog) { - this.dialog.nativeElement.close(); - } + this.dialog.nativeElement.close(); + } + + onChange(processDefinitionId) { + let processDef = this.processDefinitions.find((processDefinition) => { + return processDefinition.id === processDefinitionId; + }); + let clone = JSON.parse(JSON.stringify(processDef)); + this.currentProcessDef = clone; + } + + hasStartForm() { + return this.currentProcessDef && this.currentProcessDef.hasStartForm; + } + + isStartFormMissingOrValid() { + return !this.startForm || this.startForm.form.isValid; + } + + validateForm() { + return this.currentProcessDef.id && this.name && this.isStartFormMissingOrValid(); + } + + reset() { + this.currentProcessDef = {}; } } diff --git a/ng2-components/ng2-activiti-processlist/src/components/placeholder.spec.ts b/ng2-components/ng2-activiti-processlist/src/components/placeholder.spec.ts new file mode 100644 index 0000000000..160a6a7451 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/placeholder.spec.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. + */ + +describe('Placeholder', () => { + it('test placeholder', () => { + expect(true).toBe(true); + }); +}); diff --git a/ng2-components/ng2-activiti-processlist/src/i18n/en.json b/ng2-components/ng2-activiti-processlist/src/i18n/en.json index 5c45ffab35..448ab632ea 100644 --- a/ng2-components/ng2-activiti-processlist/src/i18n/en.json +++ b/ng2-components/ng2-activiti-processlist/src/i18n/en.json @@ -17,6 +17,7 @@ "STARTED_BY": "Started by", "STARTED": "Started", "COMMENTS": "Comments", + "START_FORM": "Start Form", "TASKS_ACTIVE": "Active Tasks", "TASKS_COMPLETED": "Completed Tasks", "TASK_SUBTITLE": "Assigned to {{user}}, created {{created}}" @@ -45,6 +46,7 @@ "TYPE": "Type", "NAME": "Name" }, + "TYPE_PLACEHOLDER": "Choose one...", "ACTION": { "START": "Start", "CANCEL": "Cancel" diff --git a/ng2-components/ng2-activiti-processlist/src/models/filter.model.ts b/ng2-components/ng2-activiti-processlist/src/models/filter.model.ts deleted file mode 100644 index 2cd0c0eb42..0000000000 --- a/ng2-components/ng2-activiti-processlist/src/models/filter.model.ts +++ /dev/null @@ -1,60 +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. - */ - -/** - * - * 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/task-details.model.ts b/ng2-components/ng2-activiti-processlist/src/models/task-details.model.ts deleted file mode 100644 index 5ab0361aaa..0000000000 --- a/ng2-components/ng2-activiti-processlist/src/models/task-details.model.ts +++ /dev/null @@ -1,93 +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. - */ - -/** - * - * 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/services/activiti-process.service.spec.ts b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts index ddfe2d0ab9..ed93bdbbe2 100644 --- 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 @@ -15,37 +15,103 @@ * limitations under the License. */ -import { it, describe, expect, beforeEachProviders, beforeEach, inject } from '@angular/core/testing'; -import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { ReflectiveInjector } from '@angular/core'; +import { + AlfrescoAuthenticationService, + AlfrescoSettingsService, + AlfrescoApiService +} from 'ng2-alfresco-core'; +import { fakeApi, fakeEmptyFilters, fakeFilters, fakeError } from '../assets/activiti-process.service.mock'; import { ActivitiProcessService } from './activiti-process.service'; -// import { ProcessInstance } from '../models/process-instance'; describe('ActivitiProcessService', () => { - let processService; + let service: ActivitiProcessService; + let injector: ReflectiveInjector; - beforeEachProviders(() => { - return [ + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ ActivitiProcessService, - AlfrescoSettingsService, - AlfrescoAuthenticationService - ]; + AlfrescoApiService, + AlfrescoAuthenticationService, + AlfrescoSettingsService + ]); + service = injector.get(ActivitiProcessService); + let authenticationService: AlfrescoAuthenticationService = injector.get(AlfrescoAuthenticationService); + spyOn(authenticationService, 'getAlfrescoApi').and.returnValue(fakeApi); }); - beforeEach(inject([ActivitiProcessService], (service: ActivitiProcessService) => { - processService = service; - })); - - it('should get process instances', (done) => { + xit('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(); - // }); + describe('filters', () => { + + let userFiltersApi = fakeApi.activiti.userFiltersApi; + let getFilters: any, createFilter: any; + + beforeEach(() => { + getFilters = spyOn(userFiltersApi, 'getUserProcessInstanceFilters'); + createFilter = spyOn(userFiltersApi, 'createUserProcessInstanceFilter'); + }); + + it('should call the API without an appId defined by default', () => { + getFilters = getFilters.and.returnValue(Promise.resolve(fakeFilters)); + service.getProcessFilters(null); + expect(getFilters).toHaveBeenCalledWith({}); + }); + + it('should call the API with the correct appId when specified', () => { + getFilters = getFilters.and.returnValue(Promise.resolve(fakeFilters)); + service.getProcessFilters('226'); + expect(getFilters).toHaveBeenCalledWith({appId: '226'}); + }); + + it('should return the non-empty filter list that is returned by the API', (done) => { + getFilters = getFilters.and.returnValue(Promise.resolve(fakeFilters)); + service.getProcessFilters(null).subscribe( + (res) => { + expect(res.length).toBe(1); + done(); + } + ); + }); + + it('should return the default filters when none are returned by the API', (done) => { + getFilters = getFilters.and.returnValue(Promise.resolve(fakeEmptyFilters)); + + service.getProcessFilters(null).subscribe( + (res) => { + expect(res.length).toBe(3); + done(); + } + ); + }); + + it('should create the default filters when none are returned by the API', (done) => { + getFilters = getFilters.and.returnValue(Promise.resolve(fakeEmptyFilters)); + createFilter = createFilter.and.returnValue(Promise.resolve({})); + + service.getProcessFilters(null).subscribe( + (res) => { + expect(createFilter).toHaveBeenCalledTimes(3); + done(); + } + ); + }); + + it('should pass on any error that is returned by the API', (done) => { + getFilters = getFilters.and.returnValue(Promise.reject(fakeError)); + + service.getProcessFilters(null).subscribe( + () => {}, + (res) => { + expect(res).toBe(fakeError); + 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 index 9f5926172e..3ad0aed595 100644 --- a/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts +++ b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts @@ -15,16 +15,16 @@ * 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 { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { ProcessInstance } from '../models/process-instance'; +import { User, Comment, FilterRepresentationModel, TaskQueryRequestRepresentationModel } from 'ng2-activiti-tasklist'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; +declare var moment: any; + @Injectable() export class ActivitiProcessService { @@ -49,8 +49,8 @@ export class ActivitiProcessService { .catch(this.handleError); } - getProcessInstances(filter: FilterModel): Observable { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstances(filter)) + getProcessInstances(requestNode: TaskQueryRequestRepresentationModel): Observable { + return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstances(requestNode)) .map(this.extractData) .catch(this.handleError); } @@ -59,11 +59,102 @@ export class ActivitiProcessService { let filterOpts = appId ? { appId: appId } : {}; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.userFiltersApi.getUserProcessInstanceFilters(filterOpts)) - .map(this.extractData) + return Observable.fromPromise(this.callApiGetUserProcessInstanceFilters(filterOpts)) + .map((response: any) => { + let filters: FilterRepresentationModel[] = []; + response.data.forEach((filter: FilterRepresentationModel) => { + let filterModel = new FilterRepresentationModel(filter); + filters.push(filterModel); + }); + if (response && response.data && response.data.length === 0) { + return this.createDefaultFilters(appId); + } + return filters; + }) .catch(this.handleError); } + /** + * Create and return the default filters + * @param appId + * @returns {FilterRepresentationModel[]} + */ + private createDefaultFilters(appId: string): FilterRepresentationModel[] { + let filters: FilterRepresentationModel[] = []; + + let involvedTasksFilter = this.getRunningFilterInstance(appId); + this.addFilter(involvedTasksFilter); + filters.push(involvedTasksFilter); + + let myTasksFilter = this.getCompletedFilterInstance(appId); + this.addFilter(myTasksFilter); + filters.push(myTasksFilter); + + let queuedTasksFilter = this.getAllFilterInstance(appId); + this.addFilter(queuedTasksFilter); + filters.push(queuedTasksFilter); + + return filters; + } + + /** + * Return a static Running filter instance + * @param appId + * @returns {FilterRepresentationModel} + */ + private getRunningFilterInstance(appId: string): FilterRepresentationModel { + return new FilterRepresentationModel({ + 'name': 'Running', + 'appId': appId, + 'recent': true, + 'icon': 'glyphicon-random', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'running'} + }); + } + + /** + * Return a static Completed filter instance + * @param appId + * @returns {FilterRepresentationModel} + */ + private getCompletedFilterInstance(appId: string): FilterRepresentationModel { + return new FilterRepresentationModel({ + 'name': 'Completed', + 'appId': appId, + 'recent': false, + 'icon': 'glyphicon-ok-sign', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'completed'} + }); + } + + /** + * Return a static All filter instance + * @param appId + * @returns {FilterRepresentationModel} + */ + private getAllFilterInstance(appId: string): FilterRepresentationModel { + return new FilterRepresentationModel({ + 'name': 'All', + 'appId': appId, + 'recent': true, + 'icon': 'glyphicon-th', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'all'} + }); + } + + /** + * Add a filter + * @param filter - FilterRepresentationModel + * @returns {FilterRepresentationModel} + */ + addFilter(filter: FilterRepresentationModel): Observable { + return Observable.fromPromise(this.callApiAddFilter(filter)) + .map(res => res) + .map((response: FilterRepresentationModel) => { + return response; + }).catch(this.handleError); + } + getProcess(id: string): Observable { return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstance(id)) .catch(this.handleError); @@ -79,7 +170,7 @@ export class ActivitiProcessService { 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); + task.created = moment(task.created, 'YYYY-MM-DD').format(); return task; })) .catch(this.handleError); @@ -96,8 +187,12 @@ export class ActivitiProcessService { .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); + let user = new User({ + id: comment.createdBy.id, + email: comment.createdBy.email, + firstName: comment.createdBy.firstName, + lastName: comment.createdBy.lastName + }); comments.push(new Comment(comment.id, comment.message, comment.created, user)); }); return comments; @@ -112,7 +207,7 @@ export class ActivitiProcessService { */ addProcessInstanceComment(id: string, message: string): Observable { return Observable.fromPromise( - this.authService.getAlfrescoApi().activiti.commentsApi.addProcessInstanceComment({message: message}, id) + this.authService.getAlfrescoApi().activiti.commentsApi.addProcessInstanceComment({message: message}, id) ) .map(res => res) .map((response: Comment) => { @@ -124,7 +219,7 @@ export class ActivitiProcessService { getProcessDefinitions(appId: string) { let opts = appId ? { latest: true, - appId: appId + appDefinitionId: appId } : { latest: true }; @@ -135,10 +230,14 @@ export class ActivitiProcessService { .catch(this.handleError); } - startProcess(processDefinitionId: string, name: string) { - let startRequest: any = {}; - startRequest.name = name; - startRequest.processDefinitionId = processDefinitionId; + startProcess(processDefinitionId: string, name: string, startFormValues?: any) { + let startRequest: any = { + name: name, + processDefinitionId: processDefinitionId + }; + if (startFormValues) { + startRequest.values = startFormValues; + } return Observable.fromPromise( this.authService.getAlfrescoApi().activiti.processApi.startNewProcessInstance(startRequest) ) @@ -152,12 +251,19 @@ export class ActivitiProcessService { .catch(this.handleError); } + private callApiGetUserProcessInstanceFilters(filterOpts) { + return this.authService.getAlfrescoApi().activiti.userFiltersApi.getUserProcessInstanceFilters(filterOpts); + } + + private callApiAddFilter(filter: FilterRepresentationModel) { + return this.authService.getAlfrescoApi().activiti.userFiltersApi.createUserProcessInstanceFilter(filter); + } + 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 index e4d2ae201a..7be35bfec8 100644 --- a/ng2-components/ng2-activiti-processlist/tsconfig.json +++ b/ng2-components/ng2-activiti-processlist/tsconfig.json @@ -1,27 +1,26 @@ { - "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" - ] + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "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", + "types": ["core-js", "jasmine", "node"] + }, + "exclude": [ + "demo", + "node_modules", + "dist" + ] } diff --git a/ng2-components/ng2-activiti-processlist/tslint.json b/ng2-components/ng2-activiti-processlist/tslint.json index ba706079f4..5509c88fc7 100644 --- a/ng2-components/ng2-activiti-processlist/tslint.json +++ b/ng2-components/ng2-activiti-processlist/tslint.json @@ -35,7 +35,7 @@ "no-arg": true, "no-bitwise": false, "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, + "no-consecutive-blank-lines": true, "no-console": [ true, "debug", diff --git a/ng2-components/ng2-activiti-processlist/typings.json b/ng2-components/ng2-activiti-processlist/typings.json deleted file mode 100644 index 7e0e18568d..0000000000 --- a/ng2-components/ng2-activiti-processlist/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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/.gitignore b/ng2-components/ng2-activiti-tasklist/.gitignore index 13324e66b9..5253e5db68 100644 --- a/ng2-components/ng2-activiti-tasklist/.gitignore +++ b/ng2-components/ng2-activiti-tasklist/.gitignore @@ -7,6 +7,7 @@ dist src/**/*.js src/**/*.js.map src/**/*.d.ts +!src/declarations.d.ts demo/**/*.js demo/**/*.js.map demo/**/*.d.ts diff --git a/ng2-components/ng2-activiti-tasklist/.npmignore b/ng2-components/ng2-activiti-tasklist/.npmignore index 16ba3e61e5..c5ca623298 100644 --- a/ng2-components/ng2-activiti-tasklist/.npmignore +++ b/ng2-components/ng2-activiti-tasklist/.npmignore @@ -1,7 +1,6 @@ npm-debug.log .idea -assets/ coverage/ node_modules typings/ diff --git a/ng2-components/ng2-activiti-tasklist/README.md b/ng2-components/ng2-activiti-tasklist/README.md index 7f2fa63e0f..bcee4ab07b 100644 --- a/ng2-components/ng2-activiti-tasklist/README.md +++ b/ng2-components/ng2-activiti-tasklist/README.md @@ -31,92 +31,225 @@

+

+ + travis
+    Status + + + travis
+    Status + + + Coverage Status + + + npm downloads + + + license + + + alfresco component + + + angular 2 + + + typescript + + + node version + +

+ +Displays lists of process instances both active and completed, using any defined process filter, and +render details of any chosen instance. + ## 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). +necessary configuration [prerequisites](https://github.com/Alfresco/alfresco-ng2-components/blob/master/PREREQUISITES.md). ## Install -```sh -npm install --save ng2-activiti-tasklist -``` +Follow the 3 steps below: -### Dependencies +1. Npm -Add the following dependency to your index.html: + ```sh + npm install ng2-activiti-tasklist --save + ``` -```html - -``` +2. Html -You must separately install the following libraries for your application: + Include these dependencies in your index.html page: -- [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) + ```html + + -```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 - - - - -``` + + + + + + ``` + +3. SystemJs + + Add the following components to your systemjs.config.js file: + + - ng2-translate + - alfresco-js-api + - ng2-alfresco-core + - ng2-activiti-form + - ng2-alfresco-datatable + - ng2-activiti-tasklist + + Please refer to the following example file: [systemjs.config.js](demo/systemjs + .config.js) . ## Basic usage example Activiti Task List -The component shows the list of all the tasks filter by the -FilterModel passed in input. + +The component shows the list of all the tasks filter by the FilterParamRepresentationModel passed in input. + ```html ``` +Usage example of this component : + +**main.ts** +```ts + +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { FilterRepresentationModel, ActivitiTaskListModule } from 'ng2-activiti-tasklist'; +import { CoreModule } from 'ng2-alfresco-core'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { ObjectDataTableAdapter, DataSorting } from 'ng2-alfresco-datatable'; + +@Component({ + selector: 'alfresco-app-demo', + template: `` +}) +class MyDemoApp { + + dataTasks: ObjectDataTableAdapter; + + filterRepresentationModel: FilterRepresentationModel; + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = 'http://localhost:9999'; + + this.authService.login('admin', 'admin').subscribe( + ticket => { + console.log(ticket); + }, + error => { + console.log(error); + }); + + this.dataTasks = new ObjectDataTableAdapter([], [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'started', title: 'Started', cssClass: 'hidden', sortable: true} + ] + ); + this.dataTasks.setSorting(new DataSorting('started', 'desc')); + + this.filterRepresentationModel = new FilterRepresentationModel({ + appId: '3003', + filter: { + processDefinitionKey: null, + assignment: 'involved', + name: null, + state: 'running', + sort: 'created-desc' + } + }); + } +} + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + ActivitiTaskListModule.forRoot() + ], + declarations: [MyDemoApp], + bootstrap: [MyDemoApp] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); + + +``` + #### 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
+ +| Name | Description | +| --- | --- | +| `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. +| Name | Description | +| --- | --- | +| `taskFilter` | { FilterParamRepresentationModel } required) FilterParamRepresentationModel 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" - } + appId: '3003', + filter:{ + processDefinitionKey: null, + name:null, + assignment: 'involved', + state:'running', + sort: 'created-desc' + } } ``` -**schemaColumn**: { any[] } optional) JSON object that represent -the number and the type of the columns that you want show + +| Name | Description | +| --- | --- | +| `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'}, @@ -127,20 +260,57 @@ Example: ``` ## Basic usage example Activiti Task Details + The component shows the details of the task id passed in input + ```html ``` #### Events -No 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()` | +| `onError` | Invoked at any error | #### Options -**taskId**: { string } required) The id of the task details that we -are asking for. +| Name | Type | Required | Description | +| --- | --- | --- | --- | +| `taskId` | {string} | required | The id of the task details that we are asking for. | +| `showNextTask` | {boolean} | optional | Automatically render the next one, when the task is completed. | +| `showFormTitle` | {boolean} | optional | Toggle rendering of the form title. | +| `readOnlyForm` | {boolean} | optional | Toggle readonly state of the form. Enforces all form widgets render readonly if enabled. | +| `showFormRefreshButton` | {boolean} | optional | Toggle rendering of the `Refresh` button. | +| `showFormSaveButton` | {boolean} | optional | Toggle rendering of the `Save` outcome button. | +| `showFormCompleteButton` | {boolean} | optional | Toggle rendering of the Form `Complete` outcome button | + +### Custom 'empty Activiti Task Details' template + +By default the Activiti Task Details provides the following message for the empty task details: + +'No Tasks' + + +This can be changed by adding the following custom html template: + +```html + + + + + +``` ## Basic usage example Activiti Filter + The component shows all the available filters. ```html @@ -148,16 +318,20 @@ The component shows all the available filters. ``` #### Events -**filterClick**: The event is emitted when the filter in the list is - clicked + +| Name | Description | +| --- | --- | +| `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 @@ -169,7 +343,7 @@ npm run build $ npm run build:w ``` -### Running unit tests +## Running unit tests ```sh npm test @@ -189,3 +363,27 @@ before performing unit testing. ```sh npm run coverage ``` + +## Demo + +If you want have a demo of how the component works, please check the demo folder : + +```sh +cd demo +npm install +npm start +``` + +## NPM scripts + +| Command | Description | +| --- | --- | +| npm run build | Build component | +| npm run build:w | Build component and keep watching the changes | +| npm run test | Run unit tests in the console | +| npm run test-browser | Run unit tests in the browser +| npm run coverage | Run unit tests and display code coverage report | + +## License + +[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE) diff --git a/ng2-components/ng2-activiti-tasklist/demo/README.md b/ng2-components/ng2-activiti-tasklist/demo/README.md index 28b3ffd976..14d905ea85 100644 --- a/ng2-components/ng2-activiti-tasklist/demo/README.md +++ b/ng2-components/ng2-activiti-tasklist/demo/README.md @@ -1,4 +1,4 @@ -# DataTable demo +# TasK list demo Install: diff --git a/ng2-components/ng2-activiti-tasklist/demo/index.html b/ng2-components/ng2-activiti-tasklist/demo/index.html index 5b926def7a..eff28149d9 100644 --- a/ng2-components/ng2-activiti-tasklist/demo/index.html +++ b/ng2-components/ng2-activiti-tasklist/demo/index.html @@ -2,33 +2,54 @@ - Angular 2 TaskList - Demo + + Alfresco Angular 2 Activiti Tasks - Demo + + + + + + + + - - + + + + + + + + + + + + + - - + + + - + diff --git a/ng2-components/ng2-activiti-tasklist/demo/package.json b/ng2-components/ng2-activiti-tasklist/demo/package.json index e80059f6b5..bd691d19e9 100644 --- a/ng2-components/ng2-activiti-tasklist/demo/package.json +++ b/ng2-components/ng2-activiti-tasklist/demo/package.json @@ -1,13 +1,12 @@ { "name": "ng2-activiti-tasklist-demo", - "description": "Alfresco Angular2 DataTable Component - Demo", + "description": "Alfresco Angular2 Task List 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", + "clean": "npm install rimraf && rimraf dist node_modules typings dist", + "postinstall": "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", @@ -17,37 +16,44 @@ }, "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", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", "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", + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", "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" + "material-design-lite": "1.2.1", + + "moment": "2.15.1", + "md-date-time-picker": "^2.2.0", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-alfresco-datatable": "0.4.0", + "ng2-activiti-tasklist": "^0.4.0" }, "devDependencies": { - "browser-sync": "2.10.0", - "concurrently": "2.0.0", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", "rimraf": "2.5.2", - "tslint": "3.8.1", - "typescript": "1.8.10", - "typings": "1.0.4", - "wsrv": "0.1.3" + "tslint": "3.15.1", + "license-check": "1.1.5", + "typescript": "^2.0.2", + "wsrv": "^0.1.5" }, "keywords": [ "angular2", diff --git a/ng2-components/ng2-activiti-tasklist/demo/src/main.ts b/ng2-components/ng2-activiti-tasklist/demo/src/main.ts index 28fa067a7d..0653dd4c1e 100644 --- a/ng2-components/ng2-activiti-tasklist/demo/src/main.ts +++ b/ng2-components/ng2-activiti-tasklist/demo/src/main.ts @@ -15,76 +15,166 @@ * 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; +import { Input, NgModule, Component, OnInit, ViewChild } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { + ActivitiTaskListModule, + AppDefinitionRepresentationModel, + FilterRepresentationModel, + ActivitiApps, + ActivitiTaskList +} from 'ng2-activiti-tasklist'; +import { CoreModule } from 'ng2-alfresco-core'; +import { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { ObjectDataTableAdapter, DataSorting } from 'ng2-alfresco-datatable'; @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] + selector: 'alfresco-app-demo', + template: ` +
+
+
+

+
+ Authentication failed to ip {{ host }} with user: admin, admin, you can still try to add a valid ticket to perform + operations. +
+
+ +
+ +
+ + + + +
+ +
+ + + +
+
+ +
+
+ + + +
+
+
+
+ Task Filters + + +
+
+ Task List + +
+
+ Task Details + +
+
+
+
+ +
+
+` }) -class ActivitiTaskListDemo implements OnInit { - - bpmHost: string = 'http://127.0.0.1:9999'; - - token: string; - - data: ObjectDataTableAdapter; +class MyDemoApp implements OnInit { authenticated: boolean; - constructor(private authService: AlfrescoAuthenticationService, - private settingsService: AlfrescoSettingsService) { - this.settingsService.setProviders('BPM'); - this.data = new ObjectDataTableAdapter([], []); + host: string = 'http://localhost:9999'; + + ticket: string; + + @ViewChild('activitiapps') + activitiapps: ActivitiApps; + + @ViewChild('tabmain') + tabMain: any; + + @ViewChild('tabheader') + tabHeader: any; + + @ViewChild('activitifilter') + activitifilter: any; + + @ViewChild('activitidetails') + activitidetails: any; + + @ViewChild('activititasklist') + activititasklist: ActivitiTaskList; + + @Input() + appId: number; + + layoutType: string; + + currentTaskId: string; + + taskSchemaColumns: any [] = []; + + taskFilter: any; + + dataTasks: ObjectDataTableAdapter; + + constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + settingsService.bpmHost = this.host; + settingsService.setProviders('BPM'); + + if (this.authService.getTicketBpm()) { + this.ticket = this.authService.getTicketBpm(); + } + + this.dataTasks = new ObjectDataTableAdapter( + [], + [ + {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, + {type: 'text', key: 'started', title: 'Started', cssClass: 'hidden', sortable: true} + ] + ); + this.dataTasks.setSorting(new DataSorting('started', 'desc')); } - ngOnInit() { + public updateTicket(): void { + localStorage.setItem('ticket-BPM', this.ticket); + } + + public updateHost(): void { + this.settingsService.bpmHost = this.host; 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); + public ngOnInit(): void { + this.login(); } login() { this.authService.login('admin', 'admin').subscribe( - token => { - console.log(token); - this.token = token; + ticket => { + console.log(ticket); + this.ticket = this.authService.getTicketBpm(); this.authenticated = true; }, error => { @@ -92,9 +182,58 @@ class ActivitiTaskListDemo implements OnInit { this.authenticated = false; }); } + + onAppClick(app: AppDefinitionRepresentationModel) { + this.appId = app.id; + + this.changeTab('apps', 'tasks'); + } + + onTaskFilterClick(event: FilterRepresentationModel) { + this.taskFilter = event; + } + + onSuccessTaskFilterList(event: any) { + this.taskFilter = this.activitifilter.getCurrentFilter(); + } + + onStartTaskSuccess(event: any) { + this.activititasklist.reload(); + } + + onSuccessTaskList(event: FilterRepresentationModel) { + this.currentTaskId = this.activititasklist.getCurrentTaskId(); + } + + onTaskRowClick(taskId) { + this.currentTaskId = taskId; + } + + onFormCompleted(form) { + this.activititasklist.load(this.taskFilter); + this.currentTaskId = null; + } + + changeTab(origin: string, destination: string) { + this.tabMain.nativeElement.children[origin].classList.remove('is-active'); + this.tabMain.nativeElement.children[destination].classList.add('is-active'); + + this.tabHeader.nativeElement.children[`${origin}-header`].classList.remove('is-active'); + this.tabHeader.nativeElement.children[`${destination}-header`].classList.add('is-active'); + } + } -bootstrap(ActivitiTaskListDemo, [ - HTTP_PROVIDERS, - ALFRESCO_CORE_PROVIDERS] -); +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + ActivitiTaskListModule.forRoot() + ], + declarations: [MyDemoApp], + bootstrap: [MyDemoApp] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/ng2-components/ng2-activiti-tasklist/demo/systemjs.config.js b/ng2-components/ng2-activiti-tasklist/demo/systemjs.config.js index 4a716d3e2a..eae9e94ae9 100644 --- a/ng2-components/ng2-activiti-tasklist/demo/systemjs.config.js +++ b/ng2-components/ng2-activiti-tasklist/demo/systemjs.config.js @@ -2,56 +2,49 @@ * 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); +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'npm:ng2-alfresco-datatable/dist', + 'ng2-activiti-form': 'npm:ng2-activiti-form/dist', + 'ng2-activiti-tasklist': 'npm:ng2-activiti-tasklist/dist' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ng2-translate': { defaultExtension: 'js' }, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-datatable': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-form': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-tasklist': { main: './index.js', defaultExtension: 'js'} + } + }); })(this); diff --git a/ng2-components/ng2-activiti-tasklist/demo/tsconfig.json b/ng2-components/ng2-activiti-tasklist/demo/tsconfig.json index f6761b5218..7be35bfec8 100644 --- a/ng2-components/ng2-activiti-tasklist/demo/tsconfig.json +++ b/ng2-components/ng2-activiti-tasklist/demo/tsconfig.json @@ -1,19 +1,26 @@ { "compilerOptions": { - "target": "ES5", - "module": "system", + "target": "es5", + "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, "removeComments": true, "declaration": true, - "outDir": "dist" + "noLib": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": false, + "noImplicitReturns": false, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "outDir": "dist", + "types": ["core-js", "jasmine", "node"] }, "exclude": [ - "dist", + "demo", "node_modules", - "typings/main", - "typings/main.d.ts" + "dist" ] } diff --git a/ng2-components/ng2-activiti-tasklist/demo/tslint.json b/ng2-components/ng2-activiti-tasklist/demo/tslint.json index 8c48e76469..55c0f8a666 100644 --- a/ng2-components/ng2-activiti-tasklist/demo/tslint.json +++ b/ng2-components/ng2-activiti-tasklist/demo/tslint.json @@ -38,7 +38,7 @@ "no-arg": true, "no-bitwise": true, "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, + "no-consecutive-blank-lines": true, "no-console": [ true, "debug", diff --git a/ng2-components/ng2-activiti-tasklist/demo/typings.json b/ng2-components/ng2-activiti-tasklist/demo/typings.json deleted file mode 100644 index d8954c2485..0000000000 --- a/ng2-components/ng2-activiti-tasklist/demo/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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 index dc72a4b407..a7f9116871 100644 --- a/ng2-components/ng2-activiti-tasklist/index.ts +++ b/ng2-components/ng2-activiti-tasklist/index.ts @@ -15,10 +15,73 @@ * 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'; +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CoreModule } from 'ng2-alfresco-core'; +import { DataTableModule } from 'ng2-alfresco-datatable'; +import { ActivitiFormModule } from 'ng2-activiti-form'; +import { ActivitiPeopleService } from './src/services/activiti-people.service'; +import { ActivitiTaskListService } from './src/services/activiti-tasklist.service'; -export * from './src/components/activiti-tasklist.component'; +import { + ActivitiApps, + ActivitiTaskList, + ActivitiTaskDetails, + ActivitiFilters, + NoTaskDetailsTemplateComponent, + ActivitiChecklist, + ActivitiComments, + ActivitiPeople, + ActivitiTaskHeader, + ActivitiStartTaskButton, + ActivitiPeopleSearch +} from './src/components/index'; -export const ALFRESCO_TASKLIST_DIRECTIVES: [any] = [ActivitiFilters, ActivitiTaskList, ActivitiTaskDetails]; +export * from './src/components/index'; +export * from './src/services/activiti-tasklist.service'; +export * from './src/models/index'; + +export const ACTIVITI_TASKLIST_DIRECTIVES: any[] = [ + NoTaskDetailsTemplateComponent, + ActivitiApps, + ActivitiFilters, + ActivitiTaskList, + ActivitiTaskDetails, + ActivitiChecklist, + ActivitiComments, + ActivitiPeople, + ActivitiTaskHeader, + ActivitiStartTaskButton, + ActivitiPeopleSearch +]; + +export const ACTIVITI_TASKLIST_PROVIDERS: any[] = [ + ActivitiTaskListService, + ActivitiPeopleService +]; + +@NgModule({ + imports: [ + CoreModule, + DataTableModule, + ActivitiFormModule + ], + declarations: [ + ...ACTIVITI_TASKLIST_DIRECTIVES + ], + providers: [ + ...ACTIVITI_TASKLIST_PROVIDERS + ], + exports: [ + ...ACTIVITI_TASKLIST_DIRECTIVES + ] +}) +export class ActivitiTaskListModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: ActivitiTaskListModule, + providers: [ + ...ACTIVITI_TASKLIST_PROVIDERS + ] + }; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/karma-test-shim.js b/ng2-components/ng2-activiti-tasklist/karma-test-shim.js index bffeaa9753..e788adc4ab 100644 --- a/ng2-components/ng2-activiti-tasklist/karma-test-shim.js +++ b/ng2-components/ng2-activiti-tasklist/karma-test-shim.js @@ -5,105 +5,104 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; __karma__.loaded = function() {}; +var builtPath = '/base/dist/'; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +var paths = { + // paths serve as alias + 'npm:': 'base/node_modules/' +}; + 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' + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // testing + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-activiti-form': 'npm:ng2-activiti-form/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'npm:ng2-alfresco-datatable/dist' }; var packages = { 'app': { main: 'main.js', defaultExtension: 'js' }, - 'rxjs': { 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, {}) + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-activiti-form': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-datatable': { main: './index.js', defaultExtension: 'js'} }; var config = { + paths: paths, 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') - ]) - }) +System.import('@angular/core/testing') + .then(initTestBed) + .then(initTesting); + +function initTestBed(){ + 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); + var coreTesting = providers[0]; + var browserTesting = providers[1]; + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); }) - .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') +// Import all spec files and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { return System.import(moduleName); - }); + }) + ) + .then(__karma__.start, __karma__.error); } diff --git a/ng2-components/ng2-activiti-tasklist/karma.conf.js b/ng2-components/ng2-activiti-tasklist/karma.conf.js index 07db0170aa..6b32ae88b5 100644 --- a/ng2-components/ng2-activiti-tasklist/karma.conf.js +++ b/ng2-components/ng2-activiti-tasklist/karma.conf.js @@ -7,26 +7,47 @@ module.exports = function (config) { 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}, + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', - {pattern: 'karma-test-shim.js', included: true, watched: true}, + // Polyfills + 'node_modules/core-js/client/shim.js', + 'node_modules/reflect-metadata/Reflect.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false}, + + // Paths loaded via module imports: + // Angular itself + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, + + 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/ng2-translate/**/*.js.map', included: false, watched: false}, + + 'karma-test-shim.js', // 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}, + // ng2-components + {pattern: 'node_modules/ng2-activiti-form/dist/**/*.*', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-alfresco-core/dist/**/*.*', included: false, served: true, watched: false}, + {pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.*', included: false, served: true, watched: false}, + // 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} @@ -72,15 +93,16 @@ module.exports = function (config) { ], // Coverage reporter generates the coverage - reporters: ['mocha', 'coverage', 'coveralls', 'kjhtml'], + 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'] + 'dist/**/!(*spec|index|*mock|*model).js': 'coverage' }, coverageReporter: { + includeAllSources: true, dir: 'coverage/', subdir: 'report', reporters: [ diff --git a/ng2-components/ng2-activiti-tasklist/package.json b/ng2-components/ng2-activiti-tasklist/package.json index 93d860c6ab..c4c91c9113 100644 --- a/ng2-components/ng2-activiti-tasklist/package.json +++ b/ng2-components/ng2-activiti-tasklist/package.json @@ -1,13 +1,10 @@ { "name": "ng2-activiti-tasklist", "description": "Activiti Angular2 Task List Component", - "version": "0.3.0", + "version": "0.4.0", "author": "Alfresco Software, Ltd.", "scripts": { - "postinstall": "typings install", - "clean": "rimraf dist node_modules typings", - "typings": "typings install", - "server": "wsrv -o -p 9875", + "clean": "npm install rimraf && rimraf dist node_modules typings", "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\"", @@ -19,10 +16,10 @@ "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", + "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 ng2-alfresco-datatable" + "travis": "npm link ng2-alfresco-core ng2-alfresco-datatable ng2-activiti-form" }, "main": "./dist/index.js", "typings": "./dist/index.d.ts", @@ -51,40 +48,44 @@ "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", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", "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" + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "moment": "2.15.1", + "md-date-time-picker": "^2.2.0", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-activiti-form": "0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-alfresco-datatable": "0.4.0" }, "devDependencies": { - "concurrently": "2.1.0", - "coveralls": "2.11.9", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", "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", @@ -93,10 +94,9 @@ "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" + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" }, "license-check-config": { "src": [ diff --git a/ng2-components/ng2-activiti-tasklist/src/assets/activiti-apps.mock.ts b/ng2-components/ng2-activiti-tasklist/src/assets/activiti-apps.mock.ts new file mode 100644 index 0000000000..99da6cd062 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/assets/activiti-apps.mock.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 { AppDefinitionRepresentationModel } from '../models/filter.model'; + +export var deployedApps = [new AppDefinitionRepresentationModel({ + id: '1', + name: 'App1', + icon: 'icon1', + deploymentId: '1' +}), new AppDefinitionRepresentationModel({ + id: '2', + name: 'App2', + icon: 'icon2', + deploymentId: '2' +}), new AppDefinitionRepresentationModel({ + id: '3', + name: 'App3', + icon: 'icon3', + deploymentId: '3' +})]; +export var nonDeployedApps = [new AppDefinitionRepresentationModel({ + id: '1', + name: '1', + icon: 'icon1' +}), new AppDefinitionRepresentationModel({ + id: '1', + name: '2', + icon: 'icon2' +}), new AppDefinitionRepresentationModel({ + id: '1', + name: '3', + icon: 'icon3' +})]; +export var defaultApp = [new AppDefinitionRepresentationModel({ + defaultAppId: 'tasks' +})]; diff --git a/ng2-components/ng2-activiti-tasklist/src/assets/task-details.component.mock.ts b/ng2-components/ng2-activiti-tasklist/src/assets/task-details.component.mock.ts new file mode 100644 index 0000000000..cb96238dcf --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/assets/task-details.component.mock.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 var mockTaskDetailsComponent = { + noTaskDetailsTemplateComponent: null +}; diff --git a/ng2-components/ng2-activiti-tasklist/src/assets/task-details.mock.ts b/ng2-components/ng2-activiti-tasklist/src/assets/task-details.mock.ts new file mode 100644 index 0000000000..ef07e36a07 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/assets/task-details.mock.ts @@ -0,0 +1,192 @@ +/*! + * @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 var taskDetailsMock = { + 'id': '91', + 'name': 'Request translation', + 'description': null, + 'category': null, + 'assignee': {'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'}, + 'created': '2016-11-03T15:25:42.749+0000', + 'dueDate': null, + 'endDate': null, + 'duration': null, + 'priority': 50, + 'parentTaskId': null, + 'parentTaskName': null, + 'processInstanceId': '86', + 'processInstanceName': null, + 'processDefinitionId': 'TranslationProcess:2:8', + 'processDefinitionName': 'Translation Process', + 'processDefinitionDescription': null, + 'processDefinitionKey': 'TranslationProcess', + 'processDefinitionCategory': 'http://www.activiti.org/processdef', + 'processDefinitionVersion': 2, + 'processDefinitionDeploymentId': '5', + 'formKey': '4', + 'processInstanceStartUserId': '1001', + 'initiatorCanCompleteTask': false, + 'adhocTaskCanBeReassigned': false, + 'taskDefinitionKey': 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + 'executionId': '86', + 'involvedPeople': [], + 'memberOfCandidateUsers': false, + 'managerOfCandidateGroup': false, + 'memberOfCandidateGroup': false +}; + +export var taskFormMock = { + 'id': 4, + 'name': 'Translation request', + 'processDefinitionId': 'TranslationProcess:2:8', + 'processDefinitionName': 'Translation Process', + 'processDefinitionKey': 'TranslationProcess', + 'taskId': '91', + 'taskName': 'Request translation', + 'taskDefinitionKey': 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + 'tabs': [], + 'fields': [{ + 'fieldType': 'ContainerRepresentation', + 'id': '1478093984155', + 'name': 'Label', + 'type': 'container', + 'value': null, + 'required': false, + 'readOnly': false, + 'overrideId': false, + 'colspan': 1, + 'placeholder': null, + 'minLength': 0, + 'maxLength': 0, + 'minValue': null, + 'maxValue': null, + 'regexPattern': null, + 'optionType': null, + 'hasEmptyValue': null, + 'options': null, + 'restUrl': null, + 'restResponsePath': null, + 'restIdProperty': null, + 'restLabelProperty': null, + 'tab': null, + 'className': null, + 'dateDisplayFormat': null, + 'layout': null, + 'sizeX': 2, + 'sizeY': 1, + 'row': -1, + 'col': -1, + 'visibilityCondition': null, + 'numberOfColumns': 2, + 'fields': { + '1': [{ + 'fieldType': 'AttachFileFieldRepresentation', + 'id': 'originalcontent', + 'name': 'Original content', + 'type': 'upload', + 'value': [], + 'required': true, + 'readOnly': false, + 'overrideId': false, + 'colspan': 1, + 'placeholder': null, + 'minLength': 0, + 'maxLength': 0, + 'minValue': null, + 'maxValue': null, + 'regexPattern': null, + 'optionType': null, + 'hasEmptyValue': null, + 'options': null, + 'restUrl': null, + 'restResponsePath': null, + 'restIdProperty': null, + 'restLabelProperty': null, + 'tab': null, + 'className': null, + 'params': { + }, + 'dateDisplayFormat': null, + 'layout': {'row': -1, 'column': -1, 'colspan': 1}, + 'sizeX': 1, + 'sizeY': 1, + 'row': -1, + 'col': -1, + 'visibilityCondition': null, + 'metaDataColumnDefinitions': [] + }], + '2': [{ + 'fieldType': 'RestFieldRepresentation', + 'id': 'language', + 'name': 'Language', + 'type': 'dropdown', + 'value': 'Choose one...', + 'required': true, + 'readOnly': false, + 'overrideId': false, + 'colspan': 1, + 'placeholder': null, + 'minLength': 0, + 'maxLength': 0, + 'minValue': null, + 'maxValue': null, + 'regexPattern': null, + 'optionType': null, + 'hasEmptyValue': true, + 'options': [{'id': 'empty', 'name': 'Choose one...'}, {'id': 'fr', 'name': 'French'}, { + 'id': 'de', + 'name': 'German' + }, {'id': 'es', 'name': 'Spanish'}], + 'restUrl': null, + 'restResponsePath': null, + 'restIdProperty': null, + 'restLabelProperty': null, + 'tab': null, + 'className': null, + 'params': {'existingColspan': 1, 'maxColspan': 1}, + 'dateDisplayFormat': null, + 'layout': {'row': -1, 'column': -1, 'colspan': 1}, + 'sizeX': 1, + 'sizeY': 1, + 'row': -1, + 'col': -1, + 'visibilityCondition': null, + 'endpoint': null, + 'requestHeaders': null + }] + } + }], + 'outcomes': [], + 'javascriptEvents': [], + 'className': '', + 'style': '', + 'customFieldTemplates': {}, + 'metadata': {}, + 'variables': [], + 'gridsterForm': false, + 'globalDateFormat': 'D-M-YYYY' +}; + +export var tasksMock = { + data: [ + taskDetailsMock + ] +}; + +export var noDataMock = { + data: [] +}; diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps-grid.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps-grid.component.css new file mode 100644 index 0000000000..75fe759764 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps-grid.component.css @@ -0,0 +1,22 @@ +.mdl-card__media { + margin: 0; +} + +.mdl-card__media > img { + max-width: 100%; +} + +.mdl-card__actions { + display: flex; + box-sizing:border-box; + align-items: center; +} +.mdl-card__actions > .mdl-button--icon { + margin-right: 3px; + margin-left: 3px; +} + +.mdl-card:hover { + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12), 0 5px 5px -3px rgba(0, 0, 0, .2); + cursor: pointer; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.css new file mode 100644 index 0000000000..fc2d10f8f5 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.css @@ -0,0 +1,134 @@ +:host { + width: 100%; +} +.logo { + position: absolute; + right: 20px; + top: 20px; +} +.logo i{ + font-size: 70px; +} +.theme-1 { + background-color: #269abc; +} +.theme-1 .logo i { + color: #168aac; +} +.theme-1 .mdl-card__actions i { + color: #168aac; +} +.theme-1 .mdl-card__actions i:hover { + color: #b7dfea; +} +.theme-2 { + background-color: #7da9b0; +} +.theme-2 .logo i { + color: #6d99a0; +} +.theme-2 .mdl-card__actions i { + color: #6d99a0; +} +.theme-2 .mdl-card__actions i:hover { + color: #def2f5; +} +.theme-3 { + background-color: #7689ab; +} +.theme-3 .logo i { + color: #66799b; +} +.theme-3 .mdl-card__actions i { + color: #66799b; +} +.theme-3 .mdl-card__actions i:hover { + color: #a2b4d6; +} +.theme-4 { + background-color: #c74e3e; +} +.theme-4 .logo i { + color: #b73e2e; +} +.theme-4 .mdl-card__actions i { + color: #b73e2e; +} +.theme-4 .mdl-card__actions i:hover { + color: #de8b80; +} +.theme-5 { + background-color: #fab96c; +} +.theme-5 .logo i { + color: #eaa95c; +} +.theme-5 .mdl-card__actions i { + color: #eaa95c; +} +.theme-5 .mdl-card__actions i:hover { + color: #fdd9ae; +} +.theme-6 { + background-color: #759d4c; +} +.theme-6 .logo i { + color: #658d3c; +} +.theme-6 .mdl-card__actions i { + color: #658d3c; +} +.theme-6 .mdl-card__actions i:hover { + color: #a8d07f; +} +.theme-7 { + background-color: #b1b489; +} +.theme-7 .logo i { + color: #a1a479; +} +.theme-7 .mdl-card__actions i { + color: #a1a479; +} +.theme-7 .mdl-card__actions i:hover { + color: #d9dcb2; +} +.theme-8 { + background-color: #a17299; +} +.theme-8 .logo i { + color: #916289; +} +.theme-8 .mdl-card__actions i { + color: #916289; +} +.theme-8 .mdl-card__actions i:hover { + color: #d0a8c9; +} +.theme-9 { + background-color: #696c67; +} +.theme-9 .logo i { + color: #595c57; +} +.theme-9 .mdl-card__actions i { + color: #595c57; +} +.theme-9 .mdl-card__actions i:hover { + color: #a6a9a4; +} +.theme-10 { + background-color: #cabb33; +} +.theme-10 .logo i { + color: #baab23; +} +.theme-10 .mdl-card__actions i { + color: #baab23; +} +.theme-10 .mdl-card__actions i:hover { + color: #efe79e; +} +.selectedIcon{ + color: #e9f1f3!important; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.html new file mode 100644 index 0000000000..eba3804202 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.html @@ -0,0 +1,24 @@ + diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.spec.ts new file mode 100644 index 0000000000..a7649b665a --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.spec.ts @@ -0,0 +1,166 @@ +/*! + * @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 { DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Observable } from 'rxjs/Rx'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; + +import { ActivitiApps } from './activiti-apps.component'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { TranslationMock } from './../assets/translation.service.mock'; +import { defaultApp, deployedApps, nonDeployedApps } from './../assets/activiti-apps.mock'; + +describe('ActivitiApps', () => { + + let componentHandler: any; + let component: ActivitiApps; + let fixture: ComponentFixture; + let debugElement: DebugElement; + let service: ActivitiTaskListService; + let getAppsSpy: jasmine.Spy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule + ], + declarations: [ + ActivitiApps + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiTaskListService + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiApps); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + service = fixture.debugElement.injector.get(ActivitiTaskListService); + + getAppsSpy = spyOn(service, 'getDeployedApplications').and.returnValue(Observable.of()); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should load apps on init', () => { + fixture.detectChanges(); + expect(getAppsSpy).toHaveBeenCalled(); + }); + + it('should emit an error when an error occurs loading apps', () => { + let emitSpy = spyOn(component.error, 'emit'); + getAppsSpy.and.returnValue(Observable.throw({})); + fixture.detectChanges(); + expect(emitSpy).toHaveBeenCalled(); + }); + + describe('layout', () => { + + it('should display a grid by default', () => { + fixture.detectChanges(); + expect(component.isGrid()).toBe(true); + expect(component.isList()).toBe(false); + }); + + it('should display a grid when configured to', () => { + component.layoutType = ActivitiApps.LAYOUT_GRID; + fixture.detectChanges(); + expect(component.isGrid()).toBe(true); + expect(component.isList()).toBe(false); + }); + + it('should display a list when configured to', () => { + component.layoutType = ActivitiApps.LAYOUT_LIST; + fixture.detectChanges(); + expect(component.isGrid()).toBe(false); + expect(component.isList()).toBe(true); + }); + + it('should throw an exception on init if unknown type configured', () => { + component.layoutType = 'unknown'; + expect(component.ngOnInit).toThrowError(); + }); + }); + + describe('display apps', () => { + + it('should display all deployed apps', () => { + getAppsSpy.and.returnValue(Observable.of(deployedApps)); + fixture.detectChanges(); + expect(debugElement.queryAll(By.css('h1')).length).toBe(3); + }); + + it('should not display undeployed apps', () => { + getAppsSpy.and.returnValue(Observable.of(nonDeployedApps)); + fixture.detectChanges(); + expect(debugElement.queryAll(By.css('h1')).length).toBe(0); + }); + + it('should display default app', () => { + getAppsSpy.and.returnValue(Observable.of(defaultApp)); + fixture.detectChanges(); + expect(debugElement.queryAll(By.css('h1')).length).toBe(1); + }); + + }); + + describe('select apps', () => { + + beforeEach(() => { + getAppsSpy.and.returnValue(Observable.of(deployedApps)); + fixture.detectChanges(); + }); + + it('should initially have no app selected', () => { + let selectedEls = debugElement.queryAll(By.css('.selectedIcon')); + expect(selectedEls.length).toBe(0); + }); + + it('should emit a click event when app selected', () => { + spyOn(component.appClick, 'emit'); + component.selectApp(deployedApps[1]); + expect(component.appClick.emit).toHaveBeenCalledWith(deployedApps[1]); + }); + + it('should have one app shown as selected after app selected', () => { + component.selectApp(deployedApps[1]); + fixture.detectChanges(); + let selectedEls = debugElement.queryAll(By.css('.selectedIcon')); + expect(selectedEls.length).toBe(1); + }); + + it('should have the correct app shown as selected after app selected', () => { + component.selectApp(deployedApps[1]); + fixture.detectChanges(); + let appEls = debugElement.queryAll(By.css('.mdl-grid > div')); + expect(appEls[1].query(By.css('.selectedIcon'))).not.toBeNull(); + }); + + }); + +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.ts new file mode 100644 index 0000000000..a0db97ca53 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-apps.component.ts @@ -0,0 +1,167 @@ +/*! + * @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, Output, EventEmitter, Input } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { AppDefinitionRepresentationModel } from '../models/filter.model'; +import { IconModel } from '../models/icon.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + +declare let componentHandler: any; + +@Component({ + selector: 'activiti-apps', + moduleId: module.id, + templateUrl: 'activiti-apps.component.html', + styleUrls: ['./activiti-apps.component.css', './activiti-apps-grid.component.css'], + providers: [ActivitiTaskListService] +}) +export class ActivitiApps implements OnInit { + + public static LAYOUT_LIST: string = 'LIST'; + public static LAYOUT_GRID: string = 'GRID'; + public static DEFAULT_TASKS_APP: string = 'tasks'; + public static DEFAULT_TASKS_APP_NAME: string = 'Task App'; + public static DEFAULT_TASKS_APP_THEME: string = 'theme-2'; + public static DEFAULT_TASKS_APP_ICON: string = 'glyphicon-asterisk'; + public static DEFAULT_TASKS_APP_MATERIAL_ICON: string = 'favorite_border'; + + @Input() + layoutType: string = ActivitiApps.LAYOUT_GRID; + + @Output() + appClick: EventEmitter = new EventEmitter(); + + @Output() + error: EventEmitter = new EventEmitter(); + + private appsObserver: Observer; + apps$: Observable; + + currentApp: AppDefinitionRepresentationModel; + + appList: AppDefinitionRepresentationModel [] = []; + + private iconsMDL: IconModel; + + /** + * Constructor + * @param translate Translate service + * @param activitiTaskList Task service + */ + constructor(private translate: AlfrescoTranslationService, + private activitiTaskList: ActivitiTaskListService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + + this.apps$ = new Observable(observer => this.appsObserver = observer).share(); + } + + ngOnInit() { + if (!this.isValidType()) { + throw( new Error(`LayoutType property allowed values: ${ActivitiApps.LAYOUT_LIST} - ${ActivitiApps.LAYOUT_GRID}`)); + } + + this.apps$.subscribe((app: any) => { + this.appList.push(app); + }); + this.iconsMDL = new IconModel(); + this.load(); + } + + private load() { + this.activitiTaskList.getDeployedApplications().subscribe( + (res) => { + res.forEach((app: AppDefinitionRepresentationModel) => { + if (app.defaultAppId === ActivitiApps.DEFAULT_TASKS_APP) { + app.name = ActivitiApps.DEFAULT_TASKS_APP_NAME; + app.theme = ActivitiApps.DEFAULT_TASKS_APP_THEME; + app.icon = ActivitiApps.DEFAULT_TASKS_APP_ICON; + this.appsObserver.next(app); + } else if (app.deploymentId) { + this.appsObserver.next(app); + } + }); + }, + (err) => { + this.error.emit(err); + } + ); + } + + /** + * Pass the selected app as next + * @param app + */ + public selectApp(app: AppDefinitionRepresentationModel) { + this.currentApp = app; + this.appClick.emit(app); + } + + /** + * Return true if the appId is the current app + * @param appId + * @returns {boolean} + */ + isSelected(appId: number): boolean { + return (this.currentApp !== undefined && appId === this.currentApp.id); + } + + /** + * Check if the value of the layoutType property is an allowed value + * @returns {boolean} + */ + isValidType(): boolean { + if (this.layoutType && (this.layoutType === ActivitiApps.LAYOUT_LIST || this.layoutType === ActivitiApps.LAYOUT_GRID)) { + return true; + } + return false; + } + + /** + * Return true if the layout type is LIST + * @returns {boolean} + */ + isList(): boolean { + return this.layoutType === ActivitiApps.LAYOUT_LIST; + } + + /** + * Return true if the layout type is GRID + * @returns {boolean} + */ + isGrid(): boolean { + return this.layoutType === ActivitiApps.LAYOUT_GRID; + } + + isEmpty(): boolean { + return this.appList.length === 0; + } + + getTheme(app: AppDefinitionRepresentationModel): string { + return app.theme ? app.theme : ''; + } + + getBackgroundIcon(app: AppDefinitionRepresentationModel): string { + return this.iconsMDL.mapGlyphiconToMaterialDesignIcons(app.icon); + } + +} 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 index 7e9e64291f..3cff4db999 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.css +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.css @@ -9,3 +9,7 @@ .material-icons:hover { color: rgb(255, 152, 0); } + +.mdl-tooltip { + will-change: unset; +} 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 index 9bfdf6e968..1f74274e2b 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html @@ -1,33 +1,33 @@ -{{ 'TASK_DETAILS.LABELS.CHECKLIST' | translate }} -
add
-
+
add
+
Add a checklist
-
+
{{ 'TASK_DETAILS.CHECKLIST.NONE' | translate }}
- -

New Task

+ +

New Check

- - + +
- - + +
-
\ No newline at end of file +
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.spec.ts new file mode 100644 index 0000000000..35ff285f11 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.spec.ts @@ -0,0 +1,172 @@ +/*! + * @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 { + CoreModule, + AlfrescoTranslationService +} from 'ng2-alfresco-core'; +import { SimpleChange } from '@angular/core'; +import { ActivitiTaskListService } from '../services/activiti-tasklist.service'; +import { ActivitiChecklist } from './activiti-checklist.component'; +import { TranslationMock } from '../assets/translation.service.mock'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { TaskDetailsModel } from '../models/task-details.model'; + +declare let jasmine: any; + +const fakeTaskDetail = new TaskDetailsModel({ + id: 'fake-check-id', + name: 'fake-check-name' +}); + +describe('Activiti Checklist Component', () => { + + let checklistComponent: ActivitiChecklist; + let fixture: ComponentFixture; + let element: HTMLElement; + let showChecklistDialog, closeCheckDialogButton; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [ActivitiChecklist], + providers: [ + {provide: AlfrescoTranslationService, useClass: TranslationMock}, + ActivitiTaskListService] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(ActivitiChecklist); + checklistComponent = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + }); + })); + + it('should show people component title', () => { + expect(element.querySelector('#checklist-label')).toBeDefined(); + expect(element.querySelector('#checklist-label')).not.toBeNull(); + }); + + it('should show no checklist message', () => { + expect(element.querySelector('#checklist-none-message')).not.toBeNull(); + expect(element.querySelector('#checklist-none-message').textContent).toContain('TASK_DETAILS.CHECKLIST.NONE'); + }); + + describe('when interact with people dialog', () => { + + beforeEach(() => { + checklistComponent.taskId = 'fake-task-id'; + checklistComponent.checklist = []; + fixture.detectChanges(); + showChecklistDialog = element.querySelector('#add-checklist'); + closeCheckDialogButton = element.querySelector('#close-check-dialog'); + }); + + it('should show dialog when clicked on add', () => { + expect(showChecklistDialog).not.toBeNull(); + showChecklistDialog.click(); + + expect(element.querySelector('#checklist-dialog')).not.toBeNull(); + expect(element.querySelector('#add-checklist-title')).not.toBeNull(); + expect(element.querySelector('#add-checklist-title').textContent).toContain('New Check'); + }); + + it('should close dialog when clicked on cancel', () => { + showChecklistDialog.click(); + expect(element.querySelector('#checklist-dialog').getAttribute('open')).not.toBeNull(); + closeCheckDialogButton.click(); + expect(element.querySelector('#checklist-dialog').getAttribute('open')).toBeNull(); + }); + }); + + describe('when there are task checklist', () => { + + beforeEach(() => { + checklistComponent.taskId = 'fake-task-id'; + checklistComponent.checklist = []; + fixture.detectChanges(); + showChecklistDialog = element.querySelector('#add-checklist'); + }); + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should show task checklist', () => { + checklistComponent.checklist.push(fakeTaskDetail); + fixture.detectChanges(); + expect(element.querySelector('#check-fake-check-id')).not.toBeNull(); + expect(element.querySelector('#check-fake-check-id').textContent).toContain('fake-check-name'); + }); + + it('should add checklist', async(() => { + showChecklistDialog.click(); + let addButtonDialog = element.querySelector('#add-check'); + addButtonDialog.click(); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: {id: 'fake-check-added-id', name: 'fake-check-added-name'} + }); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#check-fake-check-added-id')).not.toBeNull(); + expect(element.querySelector('#check-fake-check-added-id').textContent).toContain('fake-check-added-name'); + }); + })); + + it('should show load task checklist on change', async(() => { + checklistComponent.taskId = 'new-fake-task-id'; + checklistComponent.checklist.push(fakeTaskDetail); + fixture.detectChanges(); + let change = new SimpleChange(null, 'new-fake-task-id'); + checklistComponent.ngOnChanges({ + taskId: change + }); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: {data: [{id: 'fake-check-changed-id', name: 'fake-check-changed-name'}]} + }); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#check-fake-check-changed-id')).not.toBeNull(); + expect(element.querySelector('#check-fake-check-changed-id').textContent).toContain('fake-check-changed-name'); + }); + })); + + it('should show empty checklist when task id is null', async(() => { + checklistComponent.taskId = 'new-fake-task-id'; + checklistComponent.checklist.push(fakeTaskDetail); + fixture.detectChanges(); + checklistComponent.taskId = null; + let change = new SimpleChange(null, 'new-fake-task-id'); + checklistComponent.ngOnChanges({ + taskId: change + }); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#checklist-none-message')).not.toBeNull(); + expect(element.querySelector('#checklist-none-message').textContent).toContain('TASK_DETAILS.CHECKLIST.NONE'); + }); + })); + }); + +}); 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 index e2552ecaed..fd8574a92f 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts @@ -15,30 +15,27 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, Input, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } 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; +import { Observer, Observable } from 'rxjs/Rx'; @Component({ selector: 'activiti-checklist', - moduleId: __moduleName, + moduleId: module.id, templateUrl: './activiti-checklist.component.html', styleUrls: ['./activiti-checklist.component.css'], - providers: [ActivitiTaskListService], - pipes: [AlfrescoPipeTranslate] - + providers: [ActivitiTaskListService] }) -export class ActivitiChecklist implements OnInit { +export class ActivitiChecklist implements OnInit, OnChanges { @Input() taskId: string; + @Input() + readOnly: boolean = false; + @ViewChild('dialog') dialog: any; @@ -54,8 +51,7 @@ export class ActivitiChecklist implements OnInit { * @param auth * @param translate */ - constructor(private auth: AlfrescoAuthenticationService, - private translate: AlfrescoTranslationService, + constructor(private translate: AlfrescoTranslationService, private activitiTaskList: ActivitiTaskListService) { if (translate) { @@ -68,13 +64,17 @@ export class ActivitiChecklist implements OnInit { this.task$.subscribe((task: TaskDetailsModel) => { this.checklist.push(task); }); + } - if (this.taskId) { - this.load(this.taskId); + ngOnChanges(changes: SimpleChanges) { + let taskId = changes['taskId']; + if (taskId && taskId.currentValue) { + this.getTaskChecklist(taskId.currentValue); + return; } } - public load(taskId: string) { + public getTaskChecklist(taskId: string) { this.checklist = []; if (this.taskId) { this.activitiTaskList.getTaskChecklist(this.taskId).subscribe( @@ -94,6 +94,9 @@ export class ActivitiChecklist implements OnInit { public showDialog() { if (this.dialog) { + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); + } this.dialog.nativeElement.showModal(); } } 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 index 7e9e64291f..3cff4db999 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.css +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.css @@ -9,3 +9,7 @@ .material-icons:hover { color: rgb(255, 152, 0); } + +.mdl-tooltip { + will-change: unset; +} 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 index 50fb144e73..3194483e2a 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html @@ -1,8 +1,8 @@ {{ 'TASK_DETAILS.LABELS.COMMENTS' |translate }} -
add
-
- Add a comment +
add
+
+ {{ 'TASK_DETAILS.COMMENTS.ADD' | translate }}
-
+
{{ 'TASK_DETAILS.COMMENTS.NONE' | translate }}
-

New comment

+

{{ 'TASK_DETAILS.COMMENTS.DIALOG.TITLE' | translate }}

- +
- - + +
-
\ No newline at end of file + diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.spec.ts new file mode 100644 index 0000000000..0802e0d4fd --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.spec.ts @@ -0,0 +1,191 @@ +/*! + * @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 { SimpleChange } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Observable } from 'rxjs/Rx'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; +import { ActivitiFormModule } from 'ng2-activiti-form'; + +import { ActivitiComments } from './activiti-comments.component'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { TranslationMock } from './../assets/translation.service.mock'; + +describe('ActivitiTaskDetails', () => { + + let componentHandler: any; + let service: ActivitiTaskListService; + let component: ActivitiComments; + let fixture: ComponentFixture; + let getCommentsSpy: jasmine.Spy; + let addCommentSpy: jasmine.Spy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule, + ActivitiFormModule + ], + declarations: [ + ActivitiComments + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiTaskListService + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiComments); + component = fixture.componentInstance; + service = fixture.debugElement.injector.get(ActivitiTaskListService); + + getCommentsSpy = spyOn(service, 'getTaskComments').and.returnValue(Observable.of([{ + message: 'Test1' + }, { + message: 'Test2' + }, { + message: 'Test3' + }])); + addCommentSpy = spyOn(service, 'addTaskComment').and.returnValue(Observable.of({id: 123, message: 'Test'})); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should load comments when taskId specified', () => { + component.taskId = '123'; + fixture.detectChanges(); + expect(getCommentsSpy).toHaveBeenCalled(); + }); + + it('should emit an error when an error occurs loading comments', () => { + let emitSpy = spyOn(component.error, 'emit'); + getCommentsSpy.and.returnValue(Observable.throw({})); + component.taskId = '123'; + fixture.detectChanges(); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should not comments when no taskId is specified', () => { + fixture.detectChanges(); + expect(getCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should display comments when the task has comments', async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(3); + }); + })); + + it('should not display comments when the task has no comments', async(() => { + component.taskId = '123'; + getCommentsSpy.and.returnValue(Observable.of([])); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(0); + }); + })); + + describe('change detection', () => { + + let change = new SimpleChange('123', '456'); + let nullChange = new SimpleChange('123', null); + + beforeEach(async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + getCommentsSpy.calls.reset(); + }); + })); + + it('should fetch new comments when taskId changed', () => { + component.ngOnChanges({ 'taskId': change }); + expect(getCommentsSpy).toHaveBeenCalledWith('456'); + }); + + it('should NOT fetch new comments when empty changeset made', () => { + component.ngOnChanges({}); + expect(getCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should NOT fetch new comments when taskId changed to null', () => { + component.ngOnChanges({ 'taskId': nullChange }); + expect(getCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should set a placeholder message when taskId changed to null', () => { + component.ngOnChanges({ 'taskId': nullChange }); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-automation-id="comments-none"]'))).not.toBeNull(); + }); + }); + + describe('Add comment', () => { + + beforeEach(async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable(); + })); + + it('should display a dialog to the user when the Add button clicked', () => { + let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement; + let showSpy: jasmine.Spy = spyOn(dialogEl, 'showModal'); + component.showDialog(); + expect(showSpy).toHaveBeenCalled(); + }); + + it('should call service to add a comment', () => { + component.showDialog(); + component.message = 'Test comment'; + component.add(); + expect(addCommentSpy).toHaveBeenCalledWith('123', 'Test comment'); + }); + + it('should emit an error when an error occurs adding the comment', () => { + let emitSpy = spyOn(component.error, 'emit'); + addCommentSpy.and.returnValue(Observable.throw({})); + component.showDialog(); + component.message = 'Test comment'; + component.add(); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should close add dialog when close button clicked', () => { + let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement; + let closeSpy: jasmine.Spy = spyOn(dialogEl, 'close'); + component.showDialog(); + component.cancel(); + expect(closeSpy).toHaveBeenCalled(); + }); + + }); + +}); 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 index 92bb57d444..767cdb00ec 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts @@ -15,30 +15,30 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, Input, Output, OnInit, ViewChild, OnChanges, SimpleChanges, EventEmitter } from '@angular/core'; +import { AlfrescoTranslationService } 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; +import { Observer, Observable } from 'rxjs/Rx'; @Component({ selector: 'activiti-comments', - moduleId: __moduleName, + moduleId: module.id, templateUrl: './activiti-comments.component.html', styleUrls: ['./activiti-comments.component.css'], - providers: [ActivitiTaskListService], - pipes: [ AlfrescoPipeTranslate ] - + providers: [ActivitiTaskListService] }) -export class ActivitiComments implements OnInit { +export class ActivitiComments implements OnInit, OnChanges { @Input() taskId: string; + @Input() + readOnly: boolean = false; + + @Output() + error: EventEmitter = new EventEmitter(); + @ViewChild('dialog') dialog: any; @@ -51,11 +51,10 @@ export class ActivitiComments implements OnInit { /** * Constructor - * @param auth - * @param translate + * @param translate Translation service + * @param activitiTaskList Task service */ - constructor(private auth: AlfrescoAuthenticationService, - private translate: AlfrescoTranslationService, + constructor(private translate: AlfrescoTranslationService, private activitiTaskList: ActivitiTaskListService) { if (translate) { @@ -63,41 +62,53 @@ export class ActivitiComments implements OnInit { } this.comment$ = new Observable(observer => this.commentObserver = observer).share(); - } ngOnInit() { this.comment$.subscribe((comment: Comment) => { this.comments.push(comment); }); + this.getTaskComments(this.taskId); + } - if (this.taskId) { - this.load(this.taskId); + ngOnChanges(changes: SimpleChanges) { + let taskId = changes['taskId']; + if (taskId) { + if (taskId.currentValue) { + this.getTaskComments(taskId.currentValue); + } else { + this.resetComments(); + } } } - public load(taskId: string) { - this.comments = []; - if (this.taskId) { - this.activitiTaskList.getTaskComments(this.taskId).subscribe( + private getTaskComments(taskId: string) { + this.resetComments(); + if (taskId) { + this.activitiTaskList.getTaskComments(taskId).subscribe( (res: Comment[]) => { res.forEach((comment) => { this.commentObserver.next(comment); }); }, (err) => { - console.log(err); + this.error.emit(err); } ); } else { - this.comments = []; + this.resetComments(); } } + private resetComments() { + this.comments = []; + } + public showDialog() { - if (this.dialog) { - this.dialog.nativeElement.showModal(); + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); } + this.dialog.nativeElement.showModal(); } public add() { @@ -107,7 +118,7 @@ export class ActivitiComments implements OnInit { this.message = ''; }, (err) => { - console.log(err); + this.error.emit(err); } ); this.cancel(); 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 index 6b7e0a7a66..ef25795fc5 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.css +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.css @@ -1,3 +1,19 @@ .mdl-list__item { cursor: pointer; } + +.activiti-filters__entry { + cursor: pointer; +} + +.activiti-filters__entry-icon { + margin-right: 12px !important; +} + +.activiti-filters__entry.active { + color: rgb(68,138,255); +} + +.activiti-filters__entry.active .activiti-filters__entry-icon { + color: rgb(68,138,255); +} 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 index 580798c6a3..d48063665f 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.html @@ -1,10 +1,11 @@ \ 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 index d1663d2596..747897f6c8 100644 --- 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 @@ -15,25 +15,19 @@ * limitations under the License. */ -import { - it, - describe, - expect, - beforeEach -} from '@angular/core/testing'; - +import { SimpleChange } from '@angular/core'; import { ActivitiFilters } from './activiti-filters.component'; import { ActivitiTaskListService } from '../services/activiti-tasklist.service'; import { Observable } from 'rxjs/Rx'; -import { FilterModel } from '../models/filter.model'; +import { FilterRepresentationModel } 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')); + fakeGlobalFilter.push(new FilterRepresentationModel({name: 'FakeInvolvedTasks', filter: { state: 'open', assignment: 'fake-involved'}})); + fakeGlobalFilter.push(new FilterRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}})); let fakeGlobalFilterPromise = new Promise(function (resolve, reject) { resolve(fakeGlobalFilter); @@ -89,6 +83,7 @@ describe('ActivitiFilters', () => { }); it('should emit an error with a bad response', (done) => { + filterList.appId = '1'; spyOn(filterList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise)); filterList.onError.subscribe((err) => { @@ -99,10 +94,22 @@ describe('ActivitiFilters', () => { 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'); + it('should emit an error with a bad response', (done) => { + filterList.appName = 'fake-app'; + spyOn(filterList.activiti, 'getDeployedApplications').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise)); - filterList.filterClick.subscribe((filter: FilterModel) => { + filterList.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should emit an event when a filter is selected', (done) => { + let currentFilter = new FilterRepresentationModel({filter: { state: 'open', assignment: 'fake-involved'}}); + + filterList.filterClick.subscribe((filter: FilterRepresentationModel) => { expect(filter).toBeDefined(); expect(filter).toEqual(currentFilter); expect(filterList.currentFilter).toEqual(currentFilter); @@ -112,4 +119,41 @@ describe('ActivitiFilters', () => { filterList.selectFilter(currentFilter); }); + it('should reload filters by appId on binding changes', () => { + spyOn(filterList, 'getFiltersByAppId').and.stub(); + const appId = '1'; + + let change = new SimpleChange(null, appId); + filterList.ngOnChanges({ 'appId': change }); + + expect(filterList.getFiltersByAppId).toHaveBeenCalledWith(appId); + }); + + it('should reload filters by appId null on binding changes', () => { + spyOn(filterList, 'getFiltersByAppId').and.stub(); + const appId = null; + + let change = new SimpleChange(null, appId); + filterList.ngOnChanges({ 'appId': change }); + + expect(filterList.getFiltersByAppId).toHaveBeenCalledWith(appId); + }); + + it('should reload filters by app name on binding changes', () => { + spyOn(filterList, 'getFiltersByAppName').and.stub(); + const appName = 'fake-app-name'; + + let change = new SimpleChange(null, appName); + filterList.ngOnChanges({ 'appName': change }); + + expect(filterList.getFiltersByAppName).toHaveBeenCalledWith(appName); + }); + + it('should return the current filter after one is selected', () => { + let filter = new FilterRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}}); + expect(filterList.currentFilter).toBeUndefined(); + filterList.selectFilter(filter); + expect(filterList.getCurrentFilter()).toBe(filter); + }); + }); 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 index 9dff5f1289..e11c569582 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-filters.component.ts @@ -15,29 +15,26 @@ * limitations under the License. */ -import { Component, Output, EventEmitter, OnInit, Input } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, Output, EventEmitter, OnInit, Input, SimpleChanges, OnChanges } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; -import { FilterModel } from '../models/filter.model'; +import { FilterRepresentationModel } 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, + moduleId: module.id, templateUrl: './activiti-filters.component.html', styleUrls: ['activiti-filters.component.css'], - providers: [ActivitiTaskListService], - pipes: [AlfrescoPipeTranslate] - + providers: [ActivitiTaskListService] }) -export class ActivitiFilters implements OnInit { +export class ActivitiFilters implements OnInit, OnChanges { @Output() - filterClick: EventEmitter = new EventEmitter(); + filterClick: EventEmitter = new EventEmitter(); @Output() onSuccess: EventEmitter = new EventEmitter(); @@ -51,12 +48,12 @@ export class ActivitiFilters implements OnInit { @Input() appName: string; - private filterObserver: Observer; - filter$: Observable; + private filterObserver: Observer; + filter$: Observable; - currentFilter: FilterModel; + currentFilter: FilterRepresentationModel; - filters: FilterModel [] = []; + filters: FilterRepresentationModel [] = []; /** * Constructor @@ -67,7 +64,7 @@ export class ActivitiFilters implements OnInit { constructor(private auth: AlfrescoAuthenticationService, private translate: AlfrescoTranslationService, public activiti: ActivitiTaskListService) { - this.filter$ = new Observable(observer => this.filterObserver = observer).share(); + this.filter$ = new Observable(observer => this.filterObserver = observer).share(); if (translate) { translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); @@ -75,31 +72,51 @@ export class ActivitiFilters implements OnInit { } ngOnInit() { - this.filter$.subscribe((filter: FilterModel) => { + this.filter$.subscribe((filter: FilterRepresentationModel) => { this.filters.push(filter); }); - this.load(); + this.getFilters(this.appId, this.appName); } - /** - * 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); + ngOnChanges(changes: SimpleChanges) { + let appId = changes['appId']; + if (appId && (appId.currentValue || appId.currentValue === null)) { + this.getFiltersByAppId(appId.currentValue); + return; + } + let appName = changes['appName']; + if (appName && appName.currentValue) { + this.getFiltersByAppName(appName.currentValue); + return; } } - private filterByAppId(appId) { + /** + * Return the task list filtered by appId or by appName + * @param appId + * @param appName + */ + getFilters(appId?: string, appName?: string) { + if (appName) { + this.getFiltersByAppName(appName); + } else { + this.getFiltersByAppId(appId); + } + } + + /** + * Return the filter list filtered by appId + * @param appId - optional + */ + getFiltersByAppId(appId?: string) { this.activiti.getTaskListFilters(appId).subscribe( - (res: FilterModel[]) => { + (res: FilterRepresentationModel[]) => { + this.resetFilter(); res.forEach((filter) => { this.filterObserver.next(filter); }); + this.selectFirstFilter(); this.onSuccess.emit(res); }, (err) => { @@ -109,10 +126,15 @@ export class ActivitiFilters implements OnInit { ); } - private filterByAppName() { - this.activiti.getDeployedApplications(this.appName).subscribe( + /** + * Return the filter list filtered by appName + * @param appName + */ + getFiltersByAppName(appName: string) { + this.activiti.getDeployedApplications(appName).subscribe( application => { - this.filterByAppId(application.id); + this.getFiltersByAppId(application.id); + this.selectFirstFilter(); }, (err) => { console.log(err); @@ -124,8 +146,43 @@ export class ActivitiFilters implements OnInit { * Pass the selected filter as next * @param filter */ - public selectFilter(filter: FilterModel) { + public selectFilter(filter: FilterRepresentationModel) { this.currentFilter = filter; this.filterClick.emit(filter); } + + /** + * Select the first filter of a list if present + */ + private selectFirstFilter() { + if (!this.isFilterListEmpty()) { + this.currentFilter = this.filters[0]; + } else { + this.currentFilter = null; + } + } + + /** + * Return the current task + * @returns {FilterRepresentationModel} + */ + getCurrentFilter(): FilterRepresentationModel { + return this.currentFilter; + } + + /** + * Check if the filter list is empty + * @returns {boolean} + */ + isFilterListEmpty(): boolean { + return this.filters === undefined || (this.filters && this.filters.length === 0); + } + + /** + * Reset the filters properties + */ + private resetFilter() { + this.filters = []; + this.currentFilter = null; + } } diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css new file mode 100644 index 0000000000..11c70fd077 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css @@ -0,0 +1,18 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} + +.material-icons.people-search__icon:hover { + color: rgb(255, 152, 0); +} + +.fix-element-user-list{ + padding-top: 0px; + padding-right: 0px; + padding-bottom: 0px; + padding-left: 0px; +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html new file mode 100644 index 0000000000..b6e13ddeb3 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html @@ -0,0 +1,17 @@ +
+ + +
+
    +
  • + + face + {{getDisplayUser(user)}} + +
  • +
    + No user found to involve +
    +
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts new file mode 100644 index 0000000000..0c4c3bb9fe --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts @@ -0,0 +1,137 @@ +/*! + * @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 { + CoreModule, + AlfrescoTranslationService +} from 'ng2-alfresco-core'; +import { ActivitiPeopleSearch } from './activiti-people-search.component'; +import { TranslationMock } from '../assets/translation.service.mock'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { User } from '../models/user.model'; +import { Observable } from 'rxjs/Observable'; + +declare let jasmine: any; + +const fakeUser: User = new User({ + id: '1', + firstName: 'fake-name', + lastName: 'fake-last', + email: 'fake@mail.com' +}); + +const fakeSecondUser: User = new User({ + id: '2', + firstName: 'fake-involve-name', + lastName: 'fake-involve-last', + email: 'fake-involve@mail.com' +}); + +describe('Activiti People Search', () => { + + let activitiPeopleSearchComponent: ActivitiPeopleSearch; + let fixture: ComponentFixture; + let element: HTMLElement; + let componentHandler; + let userArray = [fakeUser, fakeSecondUser]; + let searchInput; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [ActivitiPeopleSearch], + providers: [ + {provide: AlfrescoTranslationService, useClass: TranslationMock}] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(ActivitiPeopleSearch); + activitiPeopleSearchComponent = fixture.componentInstance; + element = fixture.nativeElement; + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + activitiPeopleSearchComponent.results = Observable.of([]); + fixture.detectChanges(); + }); + })); + + it('should show input search text', () => { + expect(element.querySelector('#userSearchText')).toBeDefined(); + expect(element.querySelector('#userSearchText')).not.toBeNull(); + }); + + it('should show no user found to involve message', () => { + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#no-user-found')).not.toBeNull(); + expect(element.querySelector('#no-user-found').textContent).toContain('No user found to involve'); + }); + }); + + it('should show user which can be involved ', (done) => { + activitiPeopleSearchComponent.onSearch.subscribe(() => { + activitiPeopleSearchComponent.results = Observable.of(userArray); + activitiPeopleSearchComponent.ngOnInit(); + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#user-1')).not.toBeNull(); + expect(element.querySelector('#user-1').textContent) + .toContain('fake-name - fake-last'); + expect(element.querySelector('#user-2')).not.toBeNull(); + expect(element.querySelector('#user-2').textContent) + .toContain('fake-involve-name - fake-involve-last'); + done(); + }); + }); + searchInput = element.querySelector('#userSearchText'); + searchInput.value = 'fake-search'; + activitiPeopleSearchComponent.searchUser.markAsDirty(); + searchInput.dispatchEvent(new Event('input')); + }); + + it('should send an event when an user is clicked', async(() => { + activitiPeopleSearchComponent.onRowClicked.subscribe((user) => { + expect(user).toBeDefined(); + expect(user.firstName).toBe('fake-name'); + }); + activitiPeopleSearchComponent.results = Observable.of(userArray); + activitiPeopleSearchComponent.ngOnInit(); + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + let userToSelect = element.querySelector('#user-1'); + userToSelect.click(); + }); + })); + + it('should remove clicked user', async(() => { + activitiPeopleSearchComponent.results = Observable.of(userArray); + activitiPeopleSearchComponent.ngOnInit(); + fixture.detectChanges(); + let userToSelect = element.querySelector('#user-1'); + userToSelect.click(); + + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#user-1')).toBeNull(); + }); + })); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts new file mode 100644 index 0000000000..52bd3284cf --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.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. + */ + +import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { User } from '../models/user.model'; +import { Observable } from 'rxjs/Observable'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; + +declare let componentHandler: any; + +@Component({ + selector: 'activiti-people-search', + moduleId: module.id, + templateUrl: './activiti-people-search.component.html', + styleUrls: ['./activiti-people-search.component.css'] +}) + +export class ActivitiPeopleSearch implements OnInit, AfterViewInit { + + @Input() + results: Observable; + + @Output() + onSearch: EventEmitter = new EventEmitter(); + + @Output() + onRowClicked: EventEmitter = new EventEmitter(); + + searchUser: FormControl = new FormControl(); + + userList: User[] = []; + + constructor(private translate: AlfrescoTranslationService) { + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + + this.searchUser + .valueChanges + .debounceTime(200) + .subscribe((event) => { + this.onSearch.emit(event); + }); + } + + ngOnInit() { + this.results.subscribe((list) => { + this.userList = list; + }); + } + + ngAfterViewInit() { + this.setupMaterialComponents(componentHandler); + } + + setupMaterialComponents(handler?: any): boolean { + // workaround for MDL issues with dynamic components + let isUpgraded: boolean = false; + if (handler) { + handler.upgradeAllRegistered(); + isUpgraded = true; + } + return isUpgraded; + } + + onRowClick(userClicked: User) { + this.onRowClicked.emit(userClicked); + this.userList = this.userList.filter((user) => { + return user.id !== userClicked.id; + }); + } + + getDisplayUser(user: User): string { + let firstName = user.firstName && user.firstName !== 'null' ? user.firstName : 'N/A'; + let lastName = user.lastName && user.lastName !== 'null' ? user.lastName : 'N/A'; + return firstName + ' - ' + lastName; + } +} 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 index 7e9e64291f..132a106c54 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css @@ -6,6 +6,14 @@ font-weight: bolder; } -.material-icons:hover { +.material-icons.people__icon:hover { color: rgb(255, 152, 0); } + +.add-people-dialog__content { + padding: 20px 24px 2px; +} + +.mdl-tooltip { + will-change: unset; +} 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 index 5430f9b172..616afee42d 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html @@ -1,33 +1,36 @@ -{{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }} -
add
-
+
add
+
Add a person
-
+
{{ 'TASK_DETAILS.PEOPLE.NONE' | translate }}
- -

New User

-
-
- - -
+ +

Involve User

+
+ +
- - +
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts new file mode 100644 index 0000000000..7ede77c23e --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts @@ -0,0 +1,252 @@ +/*! + * @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 { + CoreModule, + AlfrescoTranslationService +} from 'ng2-alfresco-core'; +import { ActivitiPeopleService } from '../services/activiti-people.service'; +import { ActivitiPeople } from './activiti-people.component'; +import { ActivitiPeopleSearch } from './activiti-people-search.component'; +import { TranslationMock } from '../assets/translation.service.mock'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { User } from '../models/user.model'; + +declare let jasmine: any; + +const fakeUser: User = new User({ + id: 'fake-id', + firstName: 'fake-name', + lastName: 'fake-last', + email: 'fake@mail.com' +}); + +const fakeUserToInvolve: User = new User({ + id: 'fake-involve-id', + firstName: 'fake-involve-name', + lastName: 'fake-involve-last', + email: 'fake-involve@mail.com' +}); + +describe('Activiti People Component', () => { + + let activitiPeopleComponent: ActivitiPeople; + let fixture: ComponentFixture; + let element: HTMLElement; + let componentHandler; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [ActivitiPeople, ActivitiPeopleSearch], + providers: [ + {provide: AlfrescoTranslationService, useClass: TranslationMock}, + ActivitiPeopleService] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(ActivitiPeople); + activitiPeopleComponent = fixture.componentInstance; + element = fixture.nativeElement; + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + }); + })); + + it('should show people component title', () => { + expect(element.querySelector('#people-title')).toBeDefined(); + expect(element.querySelector('#people-title')).not.toBeNull(); + }); + + it('should show no people involved message', () => { + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#no-people-label')).not.toBeNull(); + expect(element.querySelector('#no-people-label').textContent).toContain('TASK_DETAILS.PEOPLE.NONE'); + }); + }); + + describe('when interact with people dialog', () => { + + beforeEach(() => { + activitiPeopleComponent.taskId = 'fake-task-id'; + activitiPeopleComponent.people = []; + fixture.detectChanges(); + }); + + it('should show dialog when clicked on add', () => { + expect(element.querySelector('#addPeople')).not.toBeNull(); + activitiPeopleComponent.showDialog(); + + expect(element.querySelector('#add-people-dialog')).not.toBeNull(); + expect(element.querySelector('#add-people-dialog-title')).not.toBeNull(); + expect(element.querySelector('#add-people-dialog-title').textContent).toContain('Involve User'); + }); + + it('should close dialog when clicked on cancel', () => { + activitiPeopleComponent.showDialog(); + expect(element.querySelector('#addPeople')).not.toBeNull(); + activitiPeopleComponent.cancel(); + let dialogWindow = element.querySelector('#add-people-dialog'); + expect(dialogWindow.getAttribute('open')).toBeNull(); + }); + }); + + describe('when there are involved people', () => { + + beforeEach(() => { + activitiPeopleComponent.taskId = 'fake-task-id'; + activitiPeopleComponent.people.push(fakeUser); + fixture.detectChanges(); + }); + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should show people involved', () => { + expect(element.querySelector('#user-fake-id')).not.toBeNull(); + expect(element.querySelector('#user-fake-id').textContent).toContain('fake-name'); + expect(element.querySelector('#user-fake-id').textContent).toContain('fake-last'); + }); + + it('should remove pepole involved', async(() => { + activitiPeopleComponent.removeInvolvedUser(fakeUser); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200 + }); + fixture.whenStable() + .then(() => { + fixture.detectChanges(); + expect(element.querySelector('#user-fake-id')).toBeNull(); + }); + })); + + it('should involve pepole', async(() => { + activitiPeopleComponent.involveUser(fakeUserToInvolve); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200 + }); + fixture.whenStable() + .then(() => { + fixture.detectChanges(); + expect(element.querySelector('#user-fake-involve-id')).not.toBeNull(); + expect(element.querySelector('#user-fake-involve-id').textContent) + .toBe('fake-involve-name fake-involve-last'); + }); + })); + + it('should return an observable with user search results', (done) => { + activitiPeopleComponent.peopleSearch$.subscribe((users) => { + expect(users.length).toBe(2); + expect(users[0].firstName).toBe('fake-test-1'); + expect(users[0].lastName).toBe('fake-last-1'); + expect(users[0].email).toBe('fake-test-1@test.com'); + expect(users[0].id).toBe(1); + done(); + }); + activitiPeopleComponent.searchUser('fake-search-word'); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: { + data: [{ + id: 1, + firstName: 'fake-test-1', + lastName: 'fake-last-1', + email: 'fake-test-1@test.com' + }, { + id: 2, + firstName: 'fake-test-2', + lastName: 'fake-last-2', + email: 'fake-test-2@test.com' + }] + } + }); + }); + + it('should return an empty list for not valid search', (done) => { + activitiPeopleComponent.peopleSearch$.subscribe((users) => { + expect(users.length).toBe(0); + done(); + }); + activitiPeopleComponent.searchUser('fake-search-word'); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: {} + }); + }); + }); + + describe('when there are errors on service call', () => { + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should log error message when search fails', async(() => { + console.log = jasmine.createSpy('log'); + activitiPeopleComponent.peopleSearch$.subscribe(() => { + expect(console.log).toHaveBeenCalledWith('Could not load users'); + }); + activitiPeopleComponent.searchUser('fake-search'); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 403 + }); + })); + + it('should not remove user if remove involved user fail', async(() => { + activitiPeopleComponent.people.push(fakeUser); + fixture.detectChanges(); + activitiPeopleComponent.removeInvolvedUser(fakeUser); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 403 + }); + fixture.whenStable() + .then(() => { + fixture.detectChanges(); + expect(element.querySelector('#user-fake-id')).not.toBeNull(); + expect(element.querySelector('#user-fake-id').textContent) + .toBe('fake-name fake-last'); + }); + })); + + it('should not involve user if involve user fail', async(() => { + activitiPeopleComponent.involveUser(fakeUserToInvolve); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 403 + }); + fixture.whenStable() + .then(() => { + fixture.detectChanges(); + expect(element.querySelector('#user-fake-id')).toBeNull(); + expect(element.querySelector('#no-people-label').textContent).toContain('TASK_DETAILS.PEOPLE.NONE'); + }); + })); + }); +}); 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 index 9509ff717d..e4fa29919e 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts @@ -15,70 +15,91 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, Input, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService } 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; +import { Observer, Observable } from 'rxjs/Rx'; +import { ActivitiPeopleService } from '../services/activiti-people.service'; @Component({ selector: 'activiti-people', - moduleId: __moduleName, + moduleId: module.id, templateUrl: './activiti-people.component.html', - styleUrls: ['./activiti-people.component.css'], - pipes: [ AlfrescoPipeTranslate ] - + styleUrls: ['./activiti-people.component.css'] }) -export class ActivitiPeople implements OnInit { +export class ActivitiPeople { @Input() people: User [] = []; + @Input() + taskId: string = ''; + + @Input() + readOnly: boolean = false; + @ViewChild('dialog') dialog: any; - private peopleObserver: Observer; - people$: Observable; + private peopleSearchObserver: Observer; + peopleSearch$: Observable; /** * Constructor - * @param auth * @param translate + * @param people service */ - constructor(private auth: AlfrescoAuthenticationService, - private translate: AlfrescoTranslationService) { - + constructor(private translate: AlfrescoTranslationService, + private peopleService: ActivitiPeopleService) { 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); - }); + this.peopleSearch$ = new Observable(observer => this.peopleSearchObserver = observer).share(); } public showDialog() { + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); + } if (this.dialog) { this.dialog.nativeElement.showModal(); } } - public add() { - alert('add people'); - - this.cancel(); - } - public cancel() { if (this.dialog) { this.dialog.nativeElement.close(); + this.peopleSearchObserver.next([]); } } + searchUser(searchedWord: string) { + this.peopleService.getWorkflowUsers(this.taskId, searchedWord) + .subscribe((users) => { + this.peopleSearchObserver.next(users); + }, error => console.log('Could not load users')); + } + + involveUser(user: User) { + this.peopleService.involveUserWithTask(this.taskId, user.id.toString()) + .subscribe(() => { + this.people.push(user); + }, error => console.error('Impossible to involve user with task')); + } + + removeInvolvedUser(user: User) { + this.peopleService.removeInvolvedUser(this.taskId, user.id.toString()) + .subscribe(() => { + this.people = this.people.filter((involvedUser) => { + return involvedUser.id !== user.id; + }); + }, error => console.error('Impossible to remove involved user from task')); + } + + getDisplayUser(user: User): string { + let firstName = user.firstName && user.firstName !== 'null' ? user.firstName : 'N/A'; + let lastName = user.lastName && user.lastName !== 'null' ? user.lastName : 'N/A'; + return firstName + ' ' + lastName; + } + } diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.css new file mode 100644 index 0000000000..ff0f8dd865 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.css @@ -0,0 +1,7 @@ +:host { + width: 100%; +} + +.activiti-label { + font-weight: bolder; +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.html new file mode 100644 index 0000000000..fb369d4477 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.html @@ -0,0 +1,27 @@ + + + +

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

+
+
+ + +
+
+ + +
+
+
+ + +
+
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.spec.ts new file mode 100644 index 0000000000..509a898e10 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.spec.ts @@ -0,0 +1,124 @@ +/*! + * @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 { + CoreModule, + AlfrescoTranslationService +} from 'ng2-alfresco-core'; +import { ActivitiTaskListService } from '../services/activiti-tasklist.service'; +import { ActivitiStartTaskButton } from './activiti-start-task.component'; +import { TranslationMock } from '../assets/translation.service.mock'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; + +declare let jasmine: any; + +describe('Activiti Start Task Component', () => { + + let activitiStartTaskButton: ActivitiStartTaskButton; + let fixture: ComponentFixture; + let element: HTMLElement; + let startTaskButton: HTMLElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CoreModule], + declarations: [ActivitiStartTaskButton], + providers: [ + {provide: AlfrescoTranslationService, useClass: TranslationMock}, + ActivitiTaskListService] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(ActivitiStartTaskButton); + activitiStartTaskButton = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + startTaskButton = element.querySelector('#start-task-button'); + }); + })); + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should show start task button', () => { + expect(element.querySelector('#start-task-button')).toBeDefined(); + expect(element.querySelector('#start-task-button')).not.toBeNull(); + expect(element.querySelector('#start-task-button').textContent).toContain('START_TASK.BUTTON'); + }); + + it('should show start dialog on press button', () => { + startTaskButton.click(); + expect(element.querySelector('#start-task-dialog')).not.toBeNull(); + expect(element.querySelector('#start-task-dialog').getAttribute('open')).not.toBeNull(); + expect(element.querySelector('#start-task-dialog-title')).not.toBeNull(); + expect(element.querySelector('#start-task-dialog-title').textContent).toContain('START_TASK.DIALOG.TITLE'); + }); + + it('should close start dialog on cancel button', () => { + startTaskButton.click(); + expect(element.querySelector('#start-task-dialog')).not.toBeNull(); + expect(element.querySelector('#start-task-dialog').getAttribute('open')).not.toBeNull(); + let cancelButton = element.querySelector('#button-cancel'); + cancelButton.click(); + expect(element.querySelector('#start-task-dialog').getAttribute('open')).toBeNull(); + }); + + it('should create new task when start is clicked', () => { + activitiStartTaskButton.onSuccess.subscribe(() => { + expect(element.querySelector('#start-task-dialog').getAttribute('open')).toBeNull(); + }); + let createTaskButton = element.querySelector('#button-start'); + startTaskButton.click(); + activitiStartTaskButton.name = 'fake-name'; + createTaskButton.click(); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200 + }); + }); + + it('alert message is showed on start error', () => { + spyOn(window, 'alert'); + activitiStartTaskButton.onSuccess.subscribe(() => { + expect(window.alert).toHaveBeenCalledWith('An error occurred while trying to add the task'); + }); + let createTaskButton = element.querySelector('#button-start'); + startTaskButton.click(); + activitiStartTaskButton.name = 'fake-name'; + createTaskButton.click(); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 403 + }); + }); + + it('should send on success event when the task is started', () => { + activitiStartTaskButton.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + }); + let createTaskButton = element.querySelector('#button-start'); + startTaskButton.click(); + activitiStartTaskButton.name = 'fake-name'; + createTaskButton.click(); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'json', + responseText: {} + }); + }); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.ts new file mode 100644 index 0000000000..f31b0e3ede --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.ts @@ -0,0 +1,103 @@ +/*! + * @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, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { TaskDetailsModel } from '../models/task-details.model'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; + +declare let componentHandler: any; +declare let dialogPolyfill: any; + +@Component({ + selector: 'activiti-start-task', + moduleId: module.id, + templateUrl: './activiti-start-task.component.html', + styleUrls: ['./activiti-start-task.component.css'] +}) +export class ActivitiStartTaskButton { + + @Input() + appId: string; + + @Output() + onSuccess: EventEmitter = new EventEmitter(); + + @ViewChild('dialog') + dialog: any; + + name: string; + description: string; + + /** + * Constructor + * @param auth + * @param translate + * @param taskService + */ + constructor(private translate: AlfrescoTranslationService, + private taskService: ActivitiTaskListService) { + + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src'); + } + } + + public start() { + if (this.name) { + this.taskService.createNewTask(new TaskDetailsModel({ + name: this.name, + description: this.description, + category: this.appId ? '' + this.appId : null + })).subscribe( + (res: any) => { + this.onSuccess.emit(res); + this.closeDialog(); + this.resetForm(); + }, + (err) => { + window.alert('An error occurred while trying to add the task'); + console.log(err); + } + ); + } + } + + public cancel() { + this.closeDialog(); + } + + public showDialog() { + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); + } + if (this.dialog) { + this.dialog.nativeElement.showModal(); + } + } + + private closeDialog() { + if (this.dialog) { + this.dialog.nativeElement.close(); + } + } + + private resetForm() { + this.name = ''; + this.description = ''; + } +} 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 index 07eaf92d80..67959204ff 100644 --- 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 @@ -1,3 +1,7 @@ :host { width: 100%; -} \ No newline at end of file +} + +.error-dialog h3 { + margin: 16px 0; +} 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 index ef26457a86..dde8190e57 100644 --- 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 @@ -1,27 +1,50 @@ -
{{ 'TASK_DETAILS.MESSAGES.NONE' | translate }}
+
+ +
+ {{ 'TASK_DETAILS.MESSAGES.NONE' | translate }} +
+

{{taskDetails.name}}

- +
- +
- +
- +
- - + + +
+

{{'TASK_DETAILS.ERROR.TITLE'|translate}}

+

{{'TASK_DETAILS.ERROR.DESCRIPTION'|translate}}

+ +
+
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.spec.ts new file mode 100644 index 0000000000..4e65534f66 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.spec.ts @@ -0,0 +1,249 @@ +/*! + * @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 { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Observable } from 'rxjs/Rx'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; +import { ActivitiFormModule, FormModel, FormOutcomeEvent, FormOutcomeModel, FormService } from 'ng2-activiti-form'; + +import { ActivitiTaskDetails } from './activiti-task-details.component'; +import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; +import { ActivitiPeopleService } from './../services/activiti-people.service'; +import { TranslationMock } from './../assets/translation.service.mock'; +import { taskDetailsMock, taskFormMock, tasksMock, noDataMock } from './../assets/task-details.mock'; + +describe('ActivitiTaskDetails', () => { + + let componentHandler: any; + let service: ActivitiTaskListService; + let formService: FormService; + let component: ActivitiTaskDetails; + let fixture: ComponentFixture; + let getTaskDetailsSpy: jasmine.Spy; + let getFormSpy: jasmine.Spy; + let getTasksSpy: jasmine.Spy; + let completeTaskSpy: jasmine.Spy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule, + ActivitiFormModule + ], + declarations: [ + ActivitiTaskDetails + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiTaskListService, + ActivitiPeopleService + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiTaskDetails); + component = fixture.componentInstance; + service = fixture.debugElement.injector.get(ActivitiTaskListService); + formService = fixture.debugElement.injector.get(FormService); + + getTaskDetailsSpy = spyOn(service, 'getTaskDetails').and.returnValue(Observable.of(taskDetailsMock)); + getFormSpy = spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock)); + getTasksSpy = spyOn(service, 'getTasks').and.returnValue(Observable.of(tasksMock)); + completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(Observable.of({})); + spyOn(service, 'getTaskComments').and.returnValue(Observable.of(noDataMock)); + spyOn(service, 'getTaskChecklist').and.returnValue(Observable.of(noDataMock)); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should load task details when taskId specified', () => { + component.taskId = '123'; + fixture.detectChanges(); + expect(getTaskDetailsSpy).toHaveBeenCalled(); + }); + + it('should not load task details when no taskId is specified', () => { + fixture.detectChanges(); + expect(getTaskDetailsSpy).not.toHaveBeenCalled(); + }); + + it('should set a placeholder message when taskId not initialised', () => { + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('TASK_DETAILS.MESSAGES.NONE'); + }); + + it('should display a form when the task has an associated form', async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('activiti-form'))).not.toBeNull(); + }); + })); + + it('should not display a form when the task does not have an associated form', async((done) => { + component.taskId = '123'; + taskDetailsMock.formKey = undefined; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('activiti-form'))).toBeNull(); + }); + })); + + describe('change detection', () => { + + let change = new SimpleChange('123', '456'); + let nullChange = new SimpleChange('123', null); + + beforeEach(async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + getTaskDetailsSpy.calls.reset(); + }); + })); + + it('should fetch new task details when taskId changed', () => { + component.ngOnChanges({ 'taskId': change }); + expect(getTaskDetailsSpy).toHaveBeenCalledWith('456'); + }); + + it('should NOT fetch new task details when empty changeset made', () => { + component.ngOnChanges({}); + expect(getTaskDetailsSpy).not.toHaveBeenCalled(); + }); + + it('should NOT fetch new task details when taskId changed to null', () => { + component.ngOnChanges({ 'taskId': nullChange }); + expect(getTaskDetailsSpy).not.toHaveBeenCalled(); + }); + + it('should set a placeholder message when taskId changed to null', () => { + component.ngOnChanges({ 'taskId': nullChange }); + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('TASK_DETAILS.MESSAGES.NONE'); + }); + }); + + describe('Form events', () => { + + beforeEach(async(() => { + component.taskId = '123'; + fixture.detectChanges(); + fixture.whenStable(); + })); + + it('should emit a save event when form saved', () => { + let emitSpy: jasmine.Spy = spyOn(component.formSaved, 'emit'); + component.onFormSaved(new FormModel()); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should emit a outcome execution event when form outcome executed', () => { + let emitSpy: jasmine.Spy = spyOn(component.executeOutcome, 'emit'); + component.onFormExecuteOutcome(new FormOutcomeEvent(new FormOutcomeModel(new FormModel()))); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should emit a complete event when form completed', () => { + let emitSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + component.onFormCompleted(new FormModel()); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should load next task when form completed', () => { + component.onComplete(); + expect(getTasksSpy).toHaveBeenCalled(); + }); + + it('should show placeholder message if there is no next task', () => { + getTasksSpy.and.returnValue(Observable.of(noDataMock)); + component.onComplete(); + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('TASK_DETAILS.MESSAGES.NONE'); + }); + + it('should emit an error event if an error occurs fetching the next task', () => { + let emitSpy: jasmine.Spy = spyOn(component.onError, 'emit'); + getTasksSpy.and.returnValue(Observable.throw({})); + component.onComplete(); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should NOT load next task when form completed if showNextTask is false', () => { + component.showNextTask = false; + component.onComplete(); + expect(getTasksSpy).not.toHaveBeenCalled(); + }); + + it('should call service to complete task when complete button clicked', () => { + component.onComplete(); + expect(completeTaskSpy).toHaveBeenCalled(); + }); + + it('should emit a complete event when complete button clicked and task completed', () => { + let emitSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + component.onComplete(); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should call service to load next task when complete button clicked', () => { + component.onComplete(); + expect(getTasksSpy).toHaveBeenCalled(); + }); + + it('should emit a load event when form loaded', () => { + let emitSpy: jasmine.Spy = spyOn(component.formLoaded, 'emit'); + component.onFormLoaded(new FormModel()); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should emit an error event when form error occurs', () => { + let emitSpy: jasmine.Spy = spyOn(component.onError, 'emit'); + component.onFormError({}); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should display a dialog to the user when a form error occurs', () => { + let dialogEl = fixture.debugElement.query(By.css('.error-dialog')).nativeElement; + let showSpy: jasmine.Spy = spyOn(dialogEl, 'showModal'); + component.onFormError({}); + expect(showSpy).toHaveBeenCalled(); + }); + + it('should close error dialog when close button clicked', () => { + let dialogEl = fixture.debugElement.query(By.css('.error-dialog')).nativeElement; + let closeSpy: jasmine.Spy = spyOn(dialogEl, 'close'); + component.onFormError({}); + component.closeErrorDialog(); + expect(closeSpy).toHaveBeenCalled(); + }); + + }); + +}); 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 index 00364a67d0..e6c73c9b47 100644 --- 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 @@ -15,35 +15,21 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild, Output, EventEmitter } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, Input, OnInit, ViewChild, Output, EventEmitter, TemplateRef, OnChanges, SimpleChanges, DebugElement } from '@angular/core'; +import { AlfrescoTranslationService, AlfrescoAuthenticationService } 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; +import { FormService, FormModel, FormOutcomeEvent } from 'ng2-activiti-form'; +import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; @Component({ selector: 'activiti-task-details', - moduleId: __moduleName, + moduleId: module.id, templateUrl: './activiti-task-details.component.html', - styleUrls: ['./activiti-task-details.component.css'], - providers: [ActivitiTaskListService, FormService], - directives: [ActivitiTaskHeader, ActivitiPeople, ActivitiComments, ActivitiChecklist, ActivitiForm], - pipes: [AlfrescoPipeTranslate] - + styleUrls: ['./activiti-task-details.component.css'] }) -export class ActivitiTaskDetails implements OnInit { - - @Input() - taskId: string; +export class ActivitiTaskDetails implements OnInit, OnChanges { @ViewChild('activiticomments') activiticomments: any; @@ -51,40 +37,58 @@ export class ActivitiTaskDetails implements OnInit { @ViewChild('activitichecklist') activitichecklist: any; - @Input() - showTitle: boolean = true; + @ViewChild('errorDialog') + errorDialog: DebugElement; @Input() - showCompleteButton: boolean = true; + taskId: string; @Input() - showSaveButton: boolean = true; + showNextTask: boolean = true; @Input() - readOnly: boolean = false; + showFormTitle: boolean = true; @Input() - showRefreshButton: boolean = true; + showFormCompleteButton: boolean = true; + + @Input() + showFormSaveButton: boolean = true; + + @Input() + readOnlyForm: boolean = false; + + @Input() + showFormRefreshButton: boolean = true; @Output() - formSaved = new EventEmitter(); + formSaved: EventEmitter = new EventEmitter(); @Output() - formCompleted = new EventEmitter(); + formCompleted: EventEmitter = new EventEmitter(); @Output() - formLoaded = new EventEmitter(); + formLoaded: EventEmitter = new EventEmitter(); + + @Output() + onError: EventEmitter = new EventEmitter(); + + @Output() + executeOutcome: EventEmitter = new EventEmitter(); taskDetails: TaskDetailsModel; - - taskForm: FormModel; + taskFormName: string = null; taskPeople: User[] = []; + noTaskDetailsTemplateComponent: TemplateRef; + /** * Constructor - * @param auth - * @param translate + * @param auth Authentication service + * @param translate Translation service + * @param activitiForm Form service + * @param activitiTaskList Task service */ constructor(private auth: AlfrescoAuthenticationService, private translate: AlfrescoTranslationService, @@ -102,49 +106,126 @@ export class ActivitiTaskDetails implements OnInit { } } - loadDetails(taskId: string) { - this.taskForm = null; + ngOnChanges(changes: SimpleChanges) { + let taskId = changes['taskId']; + if (taskId && !taskId.currentValue) { + this.reset(); + return; + } + if (taskId && taskId.currentValue) { + this.loadDetails(taskId.currentValue); + return; + } + } + + /** + * Reset the task details + */ + private reset() { + this.taskDetails = null; + } + + /** + * Check if the task has a form + * @returns {TaskDetailsModel|string|boolean} + */ + hasFormKey() { + return (this.taskDetails + && this.taskDetails.formKey + && this.taskDetails.formKey !== 'null'); + } + + isTaskActive() { + return this.taskDetails && this.taskDetails.duration === null; + } + + /** + * Load the activiti task details + * @param taskId + */ + private loadDetails(taskId: string) { this.taskPeople = []; + this.taskFormName = null; if (taskId) { this.activitiTaskList.getTaskDetails(taskId).subscribe( (res: TaskDetailsModel) => { this.taskDetails = res; + + let endDate: any = res.endDate; + this.readOnlyForm = !!(endDate && !isNaN(endDate.getTime())); + if (this.taskDetails && this.taskDetails.involvedPeople) { this.taskDetails.involvedPeople.forEach((user) => { - this.taskPeople.push(new User(user.id, user.email, user.firstName, user.lastName)); + this.taskPeople.push(new User(user)); }); - - if (this.activiticomments) { - this.activiticomments.load(this.taskDetails.id); - } - - if (this.activitichecklist) { - this.activitichecklist.load(this.taskDetails.id); - } } - console.log(this.taskDetails); - } - ); + }); } } + /** + * Retrieve the next open task + * @param processInstanceId + * @param processDefinitionId + */ + private loadNextTask(processInstanceId: string, processDefinitionId: string) { + let requestNode = new TaskQueryRequestRepresentationModel( + { + processInstanceId: processInstanceId, + processDefinitionId: processDefinitionId + } + ); + this.activitiTaskList.getTasks(requestNode).subscribe( + (response) => { + if (response.data && response.data.length > 0) { + this.taskDetails = response.data[0]; + } else { + this.reset(); + } + }, (error) => { + console.error(error); + this.onError.emit(error); + }); + } + + /** + * Complete button clicked + */ onComplete() { this.activitiTaskList.completeTask(this.taskId).subscribe( - (res) => { - console.log(res); - } + (res) => this.onFormCompleted(null) ); } - formSavedEmitter(data: any) { - this.formSaved.emit(data); + onFormSaved(form: FormModel) { + this.formSaved.emit(form); } - formCompletedEmitter(data: any) { - this.formCompleted.emit(data); + onFormCompleted(form: FormModel) { + this.formCompleted.emit(form); + if (this.showNextTask) { + this.loadNextTask(this.taskDetails.processInstanceId, this.taskDetails.processDefinitionId); + } } - formLoadedEmitter(data: any) { - this.formLoaded.emit(data); + onFormLoaded(form: FormModel) { + this.taskFormName = null; + if (form && form.name) { + this.taskFormName = form.name; + } + this.formLoaded.emit(form); + } + + onFormError(error: any) { + this.errorDialog.nativeElement.showModal(); + this.onError.emit(error); + } + + onFormExecuteOutcome(event: FormOutcomeEvent) { + this.executeOutcome.emit(event); + } + + closeErrorDialog(): void { + this.errorDialog.nativeElement.close(); } } 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 index 20fbab0626..8a10de9004 100644 --- 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 @@ -2,6 +2,10 @@ width: 100%; } -.activiti-label { - font-weight: bolder; -} \ No newline at end of file +.activiti-task-header__label { + font-weight: bold; +} + +.activiti-task-header__value { + color: rgb(68,138,255); +} 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 index c098c1d8c3..9cb0f62abd 100644 --- 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 @@ -1,18 +1,19 @@
-
- {{ 'TASK_DETAILS.LABELS.ASSIGNEE' | translate }}: - {{taskDetails.assignee.lastName }} +
+ {{ 'TASK_DETAILS.LABELS.ASSIGNEE' | translate }}: + {{ taskDetails.assignee.firstName }} {{ taskDetails.assignee.lastName }} + {{ 'TASK_DETAILS.ASSIGNEE.NONE' | translate }}
-
- - {{ 'TASK_DETAILS.LABELS.DUE' | translate }}: - {{taskDetails?.dueDate ? taskDetails.dueDate : ('TASK_DETAILS.DUE.NONE' |translate) }} - +
+ {{ 'TASK_DETAILS.LABELS.DUE' | translate }}: + {{ taskDetails.dueDate }} + {{ 'TASK_DETAILS.DUE.NONE' |translate }}
-
- {{ 'TASK_DETAILS.LABELS.FORM' | translate }}: - {{taskForm?.name ? taskForm.name : ('TASK_DETAILS.FORM.NONE' | translate) }} +
+ {{ 'TASK_DETAILS.LABELS.FORM' | translate }}: + {{ formName }} + {{ '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.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.spec.ts new file mode 100644 index 0000000000..ebb1263b7e --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-header.component.spec.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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; + +import { ActivitiTaskHeader } from './activiti-task-header.component'; +import { TranslationMock } from './../assets/translation.service.mock'; +import { taskDetailsMock } from './../assets/task-details.mock'; +import { TaskDetailsModel } from '../models/task-details.model'; + +describe('ActivitiTaskHeader', () => { + + let componentHandler: any; + let component: ActivitiTaskHeader; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule + ], + declarations: [ + ActivitiTaskHeader + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock } + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiTaskHeader); + component = fixture.componentInstance; + + component.taskDetails = new TaskDetailsModel(taskDetailsMock); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should render empty component if no form details provided', () => { + component.taskDetails = undefined; + fixture.detectChanges(); + expect(fixture.debugElement.children.length).toBe(0); + }); + + it('should display assignee', () => { + fixture.detectChanges(); + let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .activiti-task-header__value')); + expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams'); + }); + + it('should display placeholder if no assignee', () => { + component.taskDetails.assignee = null; + fixture.detectChanges(); + let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .activiti-task-header__value')); + expect(valueEl.nativeElement.innerText).toBe('TASK_DETAILS.ASSIGNEE.NONE'); + }); + + it('should display due date', () => { + component.taskDetails.dueDate = '2016-11-03T15:25:42.749+0000'; + fixture.detectChanges(); + let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-due-date"] .activiti-task-header__value')); + expect(valueEl.nativeElement.innerText).toBe('2016-11-03T15:25:42.749+0000'); + }); + + it('should display placeholder if no due date', () => { + component.taskDetails.dueDate = null; + fixture.detectChanges(); + let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-due-date"] .activiti-task-header__value')); + expect(valueEl.nativeElement.innerText).toBe('TASK_DETAILS.DUE.NONE'); + }); + + it('should display form name', () => { + component.formName = 'test form'; + fixture.detectChanges(); + let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-form-name"] .activiti-task-header__value')); + expect(valueEl.nativeElement.innerText).toBe('test form'); + }); + + it('should not display form name if no form name provided', () => { + fixture.detectChanges(); + let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-form-name"] .activiti-task-header__value')); + expect(valueEl).toBeNull(); + }); + +}); 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 index 2c87a8845f..a94f244aa1 100644 --- 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 @@ -15,68 +15,31 @@ * limitations under the License. */ -import { Component, Input, OnInit, OnChanges } from '@angular/core'; -import { AlfrescoTranslationService, AlfrescoAuthenticationService, AlfrescoPipeTranslate } from 'ng2-alfresco-core'; +import { Component, Input } from '@angular/core'; +import { AlfrescoTranslationService } 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, + moduleId: module.id, templateUrl: './activiti-task-header.component.html', - styleUrls: ['./activiti-task-header.component.css'], - providers: [ FormService ], - pipes: [ AlfrescoPipeTranslate ] - + styleUrls: ['./activiti-task-header.component.css'] }) -export class ActivitiTaskHeader implements OnInit, OnChanges { +export class ActivitiTaskHeader { + + @Input() + formName: string = null; @Input() taskDetails: TaskDetailsModel; - taskForm: FormModel; - - /** - * Constructor - * @param auth - * @param translate - */ - constructor(private auth: AlfrescoAuthenticationService, - private activitiForm: FormService, - private translate: AlfrescoTranslationService) { - + constructor(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); - } - ); - } + public hasAssignee(): boolean { + return (this.taskDetails && this.taskDetails.assignee) ? true : false; } } 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 index 4f932db173..9dbfa195b6 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.html @@ -2,7 +2,7 @@
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 index b8374faf83..bb7bfed213 100644 --- 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 @@ -15,26 +15,19 @@ * limitations under the License. */ -import { - it, - describe, - expect, - beforeEach -} from '@angular/core/testing'; - +import { SimpleChange } from '@angular/core'; import { ActivitiTaskList } from './activiti-tasklist.component'; import { ActivitiTaskListService } from '../services/activiti-tasklist.service'; -import { FilterModel } from '../models/filter.model'; +import { FilterRepresentationModel } from '../models/filter.model'; import { Observable } from 'rxjs/Rx'; -import { ObjectDataRow, DataRowEvent } from 'ng2-alfresco-datatable'; - +import { ObjectDataRow, DataRowEvent, ObjectDataTableAdapter } from 'ng2-alfresco-datatable'; describe('ActivitiTaskList', () => { let taskList: ActivitiTaskList; let fakeGlobalTask = { - size: 1, total: 12, start: 0, + size: 2, total: 2, start: 0, data: [ { id: 14, name: 'fake-long-name-fake-long-name-fake-long-name-fak50-long-name', description: null, category: null, @@ -51,10 +44,19 @@ describe('ActivitiTaskList', () => { ] }; + let fakeGlobalTotalTasks = { + size: 2, total: 2, start: 0, + data: [] + }; + let fakeGlobalTaskPromise = new Promise(function (resolve, reject) { resolve(fakeGlobalTask); }); + let fakeGlobalTotalTasksPromise = new Promise(function (resolve, reject) { + resolve(fakeGlobalTotalTasks); + }); + let fakeErrorTaskList = { error: 'wrong request' }; @@ -65,51 +67,60 @@ describe('ActivitiTaskList', () => { beforeEach(() => { let activitiSerevice = new ActivitiTaskListService(null); - taskList = new ActivitiTaskList(null, null, activitiSerevice); + taskList = new ActivitiTaskList(null, activitiSerevice); }); it('should use the default schemaColumn as default', () => { taskList.ngOnInit(); - expect(taskList.schemaColumn).toBeDefined(); - expect(taskList.schemaColumn.length).toEqual(4); + expect(taskList.data.getColumns()).toBeDefined(); + expect(taskList.data.getColumns().length).toEqual(4); }); it('should use the schemaColumn passed in input', () => { - taskList.schemaColumn = [ - {type: 'text', key: 'fake-id', title: 'Name'} - ]; + taskList.data = new ObjectDataTableAdapter( + [], + [ + {type: 'text', key: 'fake-id', title: 'Name'} + ] + ); taskList.ngOnInit(); - expect(taskList.schemaColumn).toBeDefined(); - expect(taskList.schemaColumn.length).toEqual(1); + expect(taskList.data.getColumns()).toBeDefined(); + expect(taskList.data.getColumns().length).toEqual(1); }); it('should return an empty task list when the taskFilter is not passed', () => { taskList.ngOnInit(); - expect(taskList.tasks).toBeUndefined(); + expect(taskList.data).toBeDefined(); expect(taskList.isTaskListEmpty()).toBeTruthy(); }); it('should return the filtered task list when the taskFilter is passed', (done) => { + spyOn(taskList.activiti, 'getTotalTasks').and.returnValue(Observable.fromPromise(fakeGlobalTotalTasksPromise)); spyOn(taskList.activiti, 'getTasks').and.returnValue(Observable.fromPromise(fakeGlobalTaskPromise)); - taskList.taskFilter = new FilterModel('name', false, 'icon', '', 'open', 'fake-assignee'); + taskList.taskFilter = new FilterRepresentationModel({filter: { state: 'open', assignment: 'fake-assignee'}}); taskList.onSuccess.subscribe( (res) => { expect(res).toBeDefined(); - expect(taskList.tasks).toBeDefined(); + expect(taskList.data).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'); + expect(taskList.data.getRows().length).toEqual(2); + expect(taskList.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(taskList.data.getRows()[1].getValue('name')).toEqual('Nameless task'); done(); }); taskList.ngOnInit(); }); + it('should return a currentId null when the taskList is empty', () => { + taskList.selectFirstTask(); + expect(taskList.getCurrentTaskId()).toBeNull(); + }); + 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'); + spyOn(taskList.activiti, 'getTotalTasks').and.returnValue(Observable.fromPromise(fakeErrorTaskPromise)); + taskList.taskFilter = new FilterRepresentationModel({filter: { state: 'open', assignment: 'fake-assignee'}}); taskList.onError.subscribe( (err) => { expect(err).toBeDefined(); @@ -119,6 +130,23 @@ describe('ActivitiTaskList', () => { taskList.ngOnInit(); }); + it('should reload tasks when reload() is called', (done) => { + spyOn(taskList.activiti, 'getTotalTasks').and.returnValue(Observable.fromPromise(fakeGlobalTotalTasksPromise)); + spyOn(taskList.activiti, 'getTasks').and.returnValue(Observable.fromPromise(fakeGlobalTaskPromise)); + taskList.taskFilter = new FilterRepresentationModel({filter: { state: 'open', assignment: 'fake-assignee'}}); + taskList.ngOnInit(); + taskList.onSuccess.subscribe( (res) => { + expect(res).toBeDefined(); + expect(taskList.data).toBeDefined(); + expect(taskList.isTaskListEmpty()).not.toBeTruthy(); + expect(taskList.data.getRows().length).toEqual(2); + expect(taskList.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(taskList.data.getRows()[1].getValue('name')).toEqual('Nameless task'); + done(); + }); + taskList.reload(); + }); + it('should emit row click event', (done) => { let row = new ObjectDataRow({ id: 999 @@ -127,10 +155,21 @@ describe('ActivitiTaskList', () => { taskList.rowClick.subscribe(taskId => { expect(taskId).toEqual(999); + expect(taskList.getCurrentTaskId()).toEqual(999); done(); }); taskList.onRowClick(rowEvent); }); + it('should reload task list by filter on binding changes', () => { + spyOn(taskList, 'load').and.stub(); + const taskFilter = new FilterRepresentationModel({filter: { state: 'open', assignment: 'fake-assignee'}}); + + let change = new SimpleChange(null, taskFilter); + taskList.ngOnChanges({ 'taskFilter': change }); + + expect(taskList.load).toHaveBeenCalled(); + }); + }); 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 index 848c215aa3..2b0c2d8c4b 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-tasklist.component.ts @@ -15,37 +15,27 @@ * 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 { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { ObjectDataTableAdapter, DataTableAdapter, DataRowEvent, ObjectDataRow } from 'ng2-alfresco-datatable'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; -import { FilterModel } from '../models/filter.model'; +import { FilterRepresentationModel, TaskQueryRequestRepresentationModel } from '../models/filter.model'; declare let componentHandler: any; -declare let __moduleName: string; @Component({ selector: 'activiti-tasklist', - moduleId: __moduleName, + moduleId: module.id, templateUrl: './activiti-tasklist.component.html', - styleUrls: ['./activiti-tasklist.component.css'], - directives: [ALFRESCO_DATATABLE_DIRECTIVES], - providers: [ActivitiTaskListService], - pipes: [AlfrescoPipeTranslate] - + styleUrls: ['./activiti-tasklist.component.css'] }) -export class ActivitiTaskList implements OnInit { +export class ActivitiTaskList implements OnInit, OnChanges { @Input() - taskFilter: FilterModel; + taskFilter: FilterRepresentationModel; @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} - ]; + data: DataTableAdapter; @Output() rowClick: EventEmitter = new EventEmitter(); @@ -56,56 +46,115 @@ export class ActivitiTaskList implements OnInit { @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) { + private defaultSchemaColumn: 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} + ]; + constructor(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.data) { + this.data = new ObjectDataTableAdapter( + [], + this.defaultSchemaColumn + ); + } if (this.taskFilter) { - this.load(this.taskFilter); + let requestNode = this.convertTaskUserToTaskQuery(this.taskFilter); + this.load(new TaskQueryRequestRepresentationModel(requestNode)); } } - public load(filter: FilterModel) { - this.activiti.getTasks(filter).subscribe( + ngOnChanges(changes: SimpleChanges) { + let taskFilter = changes['taskFilter']; + if (taskFilter && taskFilter.currentValue) { + let requestNode = this.convertTaskUserToTaskQuery(taskFilter.currentValue); + this.load(new TaskQueryRequestRepresentationModel(requestNode)); + return; + } + } + + public reload() { + if (this.taskFilter) { + let requestNode = this.convertTaskUserToTaskQuery(this.taskFilter); + this.load(new TaskQueryRequestRepresentationModel(requestNode)); + } + } + + public load(requestNode: TaskQueryRequestRepresentationModel) { + this.activiti.getTotalTasks(requestNode).subscribe( (res) => { - this.renderTasks(res.data); - this.onSuccess.emit(res); + requestNode.size = res.total; + this.activiti.getTasks(requestNode).subscribe( + (response) => { + let taskRow = this.createDataRow(response.data); + this.renderTasks(taskRow); + this.selectFirstTask(); + this.onSuccess.emit(response); + }, (error) => { + console.error(error); + this.onError.emit(error); + }); }, (err) => { console.error(err); this.onError.emit(err); }); } + /** + * Create an array of ObjectDataRow + * @param tasks + * @returns {ObjectDataRow[]} + */ + private createDataRow(tasks: any[]): ObjectDataRow[] { + let taskRows: ObjectDataRow[] = []; + tasks.forEach((row) => { + taskRows.push(new ObjectDataRow({ + id: row.id, + name: row.name, + created: row.created + })); + }); + return taskRows; + } + /** * 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()); + this.data.setRows(tasks); + } + + /** + * Select the first task of a tasklist if present + */ + selectFirstTask() { + if (!this.isTaskListEmpty()) { + this.currentTaskId = this.data.getRows()[0].getValue('id'); + } else { + this.currentTaskId = null; + } + } + + /** + * Return the current task + * @returns {string} + */ + getCurrentTaskId(): string { + return this.currentTaskId; } /** @@ -113,7 +162,7 @@ export class ActivitiTaskList implements OnInit { * @returns {ObjectDataTableAdapter|boolean} */ isTaskListEmpty(): boolean { - return this.tasks === undefined || (this.tasks && this.tasks.getRows() && this.tasks.getRows().length === 0); + return this.data === undefined || (this.data && this.data.getRows() && this.data.getRows().length === 0); } /** @@ -133,12 +182,24 @@ export class ActivitiTaskList implements OnInit { */ 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) + '...'; + t.obj.name = t.obj.name || 'Nameless task'; + if (t.obj.name.length > 50) { + t.obj.name = t.obj.name.substring(0, 50) + '...'; } return t; }); return tasks; } + + private convertTaskUserToTaskQuery(userTask: FilterRepresentationModel) { + let requestNode = { + appDefinitionId: userTask.appId, + processDefinitionId: userTask.filter.processDefinitionId, + text: userTask.filter.name, + assignment: userTask.filter.assignment, + state: userTask.filter.state, + sort: userTask.filter.sort + }; + return new TaskQueryRequestRepresentationModel(requestNode); + } } diff --git a/ng2-components/ng2-activiti-tasklist/src/components/index.ts b/ng2-components/ng2-activiti-tasklist/src/components/index.ts new file mode 100644 index 0000000000..cef995cbf1 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/index.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 * from './activiti-apps.component'; +export * from './activiti-tasklist.component'; +export * from './activiti-checklist.component'; +export * from './activiti-comments.component'; +export * from './activiti-people.component'; +export * from './activiti-task-header.component'; +export * from './no-task-detail-template.component'; +export * from './activiti-filters.component'; +export * from './activiti-task-details.component'; +export * from './activiti-start-task.component'; +export * from './activiti-people-search.component'; diff --git a/ng2-components/ng2-activiti-tasklist/src/components/no-task-detail-template.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/no-task-detail-template.component.spec.ts new file mode 100644 index 0000000000..5bf34e0165 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/no-task-detail-template.component.spec.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. + */ + +import { NoTaskDetailsTemplateComponent } from './no-task-detail-template.component'; +import { ActivitiTaskDetails } from './activiti-task-details.component'; + +describe('NoTaskDetailsTemplateComponent', () => { + + let component: NoTaskDetailsTemplateComponent; + let detailsComponent: ActivitiTaskDetails; + + beforeEach(() => { + detailsComponent = new ActivitiTaskDetails(null, null, null, null); + component = new NoTaskDetailsTemplateComponent(detailsComponent); + }); + + it('should set "no task details" template on task details component', () => { + let testTemplate = 'blah blah'; + component.template = testTemplate; + component.ngAfterContentInit(); + expect(detailsComponent.noTaskDetailsTemplateComponent).toBe(testTemplate); + }); + +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/no-task-detail-template.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/no-task-detail-template.component.ts new file mode 100644 index 0000000000..0761f1e4c5 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/no-task-detail-template.component.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 { + Directive, + ContentChild, + TemplateRef, + AfterContentInit +} from '@angular/core'; +import { ActivitiTaskDetails } from './activiti-task-details.component'; + +@Directive({ + selector: 'no-task-details-template' +}) +export class NoTaskDetailsTemplateComponent implements AfterContentInit { + + @ContentChild(TemplateRef) + template: any; + + constructor( + private activitiTaskDetails: ActivitiTaskDetails) { + } + + ngAfterContentInit() { + this.activitiTaskDetails.noTaskDetailsTemplateComponent = this.template; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/declarations.d.ts b/ng2-components/ng2-activiti-tasklist/src/declarations.d.ts new file mode 100644 index 0000000000..6ea5002752 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/declarations.d.ts @@ -0,0 +1,19 @@ +/*! + * @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. + */ + +declare let componentHandler: any; +declare let dialogPolyfill: any; diff --git a/ng2-components/ng2-activiti-tasklist/src/i18n/en.json b/ng2-components/ng2-activiti-tasklist/src/i18n/en.json index d83089f9f6..2b662dfe4d 100644 --- a/ng2-components/ng2-activiti-tasklist/src/i18n/en.json +++ b/ng2-components/ng2-activiti-tasklist/src/i18n/en.json @@ -13,6 +13,9 @@ "COMMENTS": "Comments", "CHECKLIST": "Checklist" }, + "BUTTON": { + "COMPLETE": "Complete" + }, "MESSAGES": { "NONE": "No task details found." }, @@ -22,19 +25,52 @@ "DUE": { "NONE": "No due date." }, + "ASSIGNEE": { + "NONE": "No assignee." + }, "PEOPLE": { "NONE": "No people involved." }, "COMMENTS": { - "NONE": "No comments." + "NONE": "No comments.", + "ADD": "Add a comment", + "DIALOG": { + "TITLE": "New comment", + "LABELS": { + "MESSAGE": "Message" + }, + "BUTTON": { + "ADD": "Add Comment", + "CANCEL": "Cancel" + } + } }, "CHECKLIST": { "NONE": "No checklist." + }, + "ERROR": { + "TITLE": "Something went wrong", + "DESCRIPTION": "Could not complete the specified action. Please try again or check that you have permission.", + "CLOSE": "Close" } }, "TASK_FILTERS": { "MESSAGES": { "NONE": "No task filter selected." } + }, + "START_TASK": { + "BUTTON": "Start Task", + "DIALOG": { + "TITLE": "Start Task", + "LABEL": { + "NAME": "Name", + "DESCRIPTION": "Description" + }, + "ACTION": { + "START": "Start", + "CANCEL": "Cancel" + } + } } -} \ 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 index 62021c250d..66528d70dc 100644 --- a/ng2-components/ng2-activiti-tasklist/src/i18n/it.json +++ b/ng2-components/ng2-activiti-tasklist/src/i18n/it.json @@ -8,7 +8,10 @@ "LABELS": { "ASSIGNEE": "Assegnatario", "DUE": "Scadenza", - "FORM": "Form" + "FORM": "Form", + "PEOPLE": "Persone", + "COMMENTS": "Commenti", + "CHECKLIST": "Checklist" }, "MESSAGES": { "NONE": "Nessun dettaglio task trovato." @@ -25,4 +28,4 @@ "NONE": "Nessun filtro task selezionato." } } -} \ No newline at end of file +} diff --git a/ng2-components/ng2-activiti-tasklist/src/models/filter.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/filter.model.ts index 22acd2523b..72fd681bcd 100644 --- a/ng2-components/ng2-activiti-tasklist/src/models/filter.model.ts +++ b/ng2-components/ng2-activiti-tasklist/src/models/filter.model.ts @@ -15,25 +15,60 @@ * limitations under the License. */ +/** + * + * This object represent the app definition. + * + * + * @returns {AppDefinitionRepresentationModel} . + */ +export class AppDefinitionRepresentationModel { + defaultAppId: string; + deploymentId: string; + name: string; + description: string; + theme: string; + icon: string; + id: number; + modelId: number; + tenantId: number; + + constructor(obj?: any) { + this.defaultAppId = obj && obj.defaultAppId || null; + this.deploymentId = obj && obj.deploymentId || false; + this.name = obj && obj.name || null; + this.description = obj && obj.description || null; + this.theme = obj && obj.theme || null; + this.icon = obj && obj.icon || null; + this.id = obj && obj.id; + this.modelId = obj && obj.modelId; + this.tenantId = obj && obj.tenantId; + } +} + /** * * This object represent the filter. * * - * @returns {FilterModel} . + * @returns {FilterRepresentationModel} . */ -export class FilterModel { +export class FilterRepresentationModel { id: number; + appId: string; name: string; - recent: boolean = false; + recent: boolean; icon: string; - filter: FilterParamsModel; + filter: FilterParamRepresentationModel; + index: 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); + constructor(obj?: any) { + this.appId = obj && obj.appId || null; + this.name = obj && obj.name || null; + this.recent = obj && obj.recent || false; + this.icon = obj && obj.icon || null; + this.filter = new FilterParamRepresentationModel(obj.filter); + this.index = obj && obj.index; } } @@ -42,18 +77,52 @@ export class FilterModel { * This object represent the parameters of a filter. * * - * @returns {FilterModel} . + * @returns {FilterParamRepresentationModel} . */ -export class FilterParamsModel { +export class FilterParamRepresentationModel { + processDefinitionId: string; + processDefinitionKey: string; name: string; state: string; + sort: string; assignment: string; - appDefinitionId: string; + dueAfter: Date; + dueBefore: Date; - constructor(query: string, state: string, assignment: string, appDefinitionId?: string) { - this.name = query; - this.state = state; - this.assignment = assignment; - this.appDefinitionId = appDefinitionId; + constructor(obj?: any) { + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.processDefinitionKey = obj && obj.processDefinitionKey || null; + this.name = obj && obj.name || null; + this.state = obj && obj.state || null; + this.sort = obj && obj.sort || null; + this.assignment = obj && obj.assignment || null; + this.dueAfter = obj && obj.dueAfter || null; + this.dueBefore = obj && obj.dueBefore || null; + } +} + +export class TaskQueryRequestRepresentationModel { + appDefinitionId: string; + processInstanceId: string; + processDefinitionId: string; + processDefinitionKey: string; + text: string; + assignment: string; + state: string; + sort: string; + page: number; + size: number; + + constructor(obj?: any) { + this.appDefinitionId = obj && obj.appDefinitionId || null; + this.processInstanceId = obj && obj.processInstanceId || null; + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.processDefinitionKey = obj && obj.processDefinitionKey || null; + this.text = obj && obj.text || null; + this.assignment = obj && obj.assignment || null; + this.state = obj && obj.state || null; + this.sort = obj && obj.sort || null; + this.page = obj && obj.page || 0; + this.size = obj && obj.size || 25; } } diff --git a/ng2-components/ng2-activiti-tasklist/src/models/icon.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/icon.model.ts new file mode 100644 index 0000000000..031d024d5e --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/models/icon.model.ts @@ -0,0 +1,162 @@ +/*! + * @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 IconModel { + public static DEFAULT_TASKS_APP_MATERIAL_ICON: string = 'favorite_border'; + + private iconsMDL: Map; + + constructor() { + this.initIconsMDL(); + } + + mapGlyphiconToMaterialDesignIcons(icon: string) { + return this.iconsMDL.get(icon) ? this.iconsMDL.get(icon) : IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON; + } + + /** + * Map all the bootstrap glyphicon icons with Material design material icon + */ + initIconsMDL() { + this.iconsMDL = new Map(); + + this.iconsMDL.set('glyphicon-asterisk', 'ac_unit'); + this.iconsMDL.set('glyphicon-plus', 'add'); + this.iconsMDL.set('glyphicon-euro', 'euro_symbol'); + this.iconsMDL.set('glyphicon-cloud', 'cloud'); + this.iconsMDL.set('glyphicon-envelope', 'mail'); + this.iconsMDL.set('glyphicon-pencil', 'create'); + this.iconsMDL.set('glyphicon-glass', 'local_bar'); + this.iconsMDL.set('glyphicon-music', 'music_note'); + this.iconsMDL.set('glyphicon-search', 'search'); + this.iconsMDL.set('glyphicon-heart', 'favorite'); + this.iconsMDL.set('glyphicon-heart-empty', 'favorite_border'); + this.iconsMDL.set('glyphicon-star', 'star'); + this.iconsMDL.set('glyphicon-star-empty', 'star_border'); + this.iconsMDL.set('glyphicon-user', 'person'); + this.iconsMDL.set('glyphicon-film', 'movie_creation'); + this.iconsMDL.set('glyphicon-th-large', 'view_comfy'); + this.iconsMDL.set('glyphicon-th', 'view_compact'); + this.iconsMDL.set('glyphicon-th-list', 'list'); + this.iconsMDL.set('glyphicon-ok', 'done'); + this.iconsMDL.set('glyphicon-remove', 'cancel'); + this.iconsMDL.set('glyphicon-zoom-in', 'zoom_in'); + this.iconsMDL.set('glyphicon-zoom-out', 'zoom_out'); + this.iconsMDL.set('glyphicon-off', 'highlight_off'); + this.iconsMDL.set('glyphicon-signal', 'signal_cellular_4_bar'); + this.iconsMDL.set('glyphicon-cog', 'settings'); + this.iconsMDL.set('glyphicon-trash', 'delete'); + this.iconsMDL.set('glyphicon-home', 'home'); + this.iconsMDL.set('glyphicon-file', 'insert_drive_file'); + this.iconsMDL.set('glyphicon-time', 'access_time'); + this.iconsMDL.set('glyphicon-road', 'map'); + this.iconsMDL.set('glyphicon-download-alt', 'file_download'); + this.iconsMDL.set('glyphicon-download', 'file_download'); + this.iconsMDL.set('glyphicon-upload', 'file_upload'); + this.iconsMDL.set('glyphicon-inbox', 'inbox'); + this.iconsMDL.set('glyphicon-play-circle', 'play_circle_outline'); + this.iconsMDL.set('glyphicon-repeat', 'refresh'); + this.iconsMDL.set('glyphicon-refresh', 'sync'); + this.iconsMDL.set('glyphicon-list-alt', 'event_note'); + this.iconsMDL.set('glyphicon-lock', 'lock_outline'); + this.iconsMDL.set('glyphicon-flag', 'assistant_photo'); + this.iconsMDL.set('glyphicon-headphones', 'headset'); + this.iconsMDL.set('glyphicon-volume-up', 'volume_up'); + this.iconsMDL.set('glyphicon-tag', 'local_offer'); + this.iconsMDL.set('glyphicon-tags', 'local_offer'); + this.iconsMDL.set('glyphicon-book', 'library_books'); + this.iconsMDL.set('glyphicon-bookmark', 'collections_bookmark'); + this.iconsMDL.set('glyphicon-print', 'local_printshop'); + this.iconsMDL.set('glyphicon-camera', 'local_see'); + this.iconsMDL.set('glyphicon-list', 'view_list'); + this.iconsMDL.set('glyphicon-facetime-video', 'video_call'); + this.iconsMDL.set('glyphicon-picture', 'photo'); + this.iconsMDL.set('glyphicon-map-marker', 'add_location'); + this.iconsMDL.set('glyphicon-adjust', 'brightness_4'); + this.iconsMDL.set('glyphicon-tint', 'invert_colors'); + this.iconsMDL.set('glyphicon-edit', 'edit'); + this.iconsMDL.set('glyphicon-share', 'share'); + this.iconsMDL.set('glyphicon-check', 'assignment_turned_in'); + this.iconsMDL.set('glyphicon-move', 'open_with'); + this.iconsMDL.set('glyphicon-play', 'play_arrow'); + this.iconsMDL.set('glyphicon-eject', 'eject'); + this.iconsMDL.set('glyphicon-plus-sign', 'add_circle'); + this.iconsMDL.set('glyphicon-minus-sign', 'remove_circle'); + this.iconsMDL.set('glyphicon-remove-sign', 'cancel'); + this.iconsMDL.set('glyphicon-ok-sign', 'check_circle'); + this.iconsMDL.set('glyphicon-question-sign', 'help'); + this.iconsMDL.set('glyphicon-info-sign', 'info'); + this.iconsMDL.set('glyphicon-screenshot', 'flare'); + this.iconsMDL.set('glyphicon-remove-circle', 'cancel'); + this.iconsMDL.set('glyphicon-ok-circle', 'add_circle'); + this.iconsMDL.set('glyphicon-ban-circle', 'block'); + this.iconsMDL.set('glyphicon-share-alt', 'redo'); + this.iconsMDL.set('glyphicon-exclamation-sign', 'error'); + this.iconsMDL.set('glyphicon-gift', 'giftcard'); + this.iconsMDL.set('glyphicon-leaf', 'spa'); + this.iconsMDL.set('glyphicon-fire', 'whatshot'); + this.iconsMDL.set('glyphicon-eye-open', 'remove_red_eye'); + this.iconsMDL.set('glyphicon-eye-close', 'remove_red_eye'); + this.iconsMDL.set('glyphicon-warning-sign', 'warning'); + this.iconsMDL.set('glyphicon-plane', 'airplanemode_active'); + this.iconsMDL.set('glyphicon-calendar', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-random', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-comment', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-magnet', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-retweet', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-shopping-cart', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-folder-close', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-folder-open', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-hdd', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-bullhorn', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-bell', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-certificate', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-thumbs-up', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-thumbs-down', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-hand-left', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-globe', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-wrench', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-tasks', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-filter', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-briefcase', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-dashboard', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-paperclip', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-link', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-phone', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-pushpin', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-usd', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-gbp', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-sort', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-flash', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-record', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-save', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-open', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-saved', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-send', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-floppy-disk', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-credit-card', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-cutlery', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-earphone', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-phone-alt', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-tower', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-stats', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-cloud-download', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-cloud-upload', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-tree-conifer', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + this.iconsMDL.set('glyphicon-tree-deciduous', IconModel.DEFAULT_TASKS_APP_MATERIAL_ICON); + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/models/index.ts b/ng2-components/ng2-activiti-tasklist/src/models/index.ts new file mode 100644 index 0000000000..2d3fb08a48 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/models/index.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 * from './comment.model'; +export * from './filter.model'; +export * from './icon.model'; +export * from './user.model'; +export * from './task-details.model'; 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 index 5ab0361aaa..7407656360 100644 --- a/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts +++ b/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts @@ -38,10 +38,10 @@ export class TaskDetailsModel { endDate: string; executionId: string; formKey: string; - initiatorCanCompleteTask: boolean = false; - managerOfCandidateGroup: boolean = false; - memberOfCandidateGroup: boolean = false; - memberOfCandidateUsers: boolean = false; + initiatorCanCompleteTask: boolean; + managerOfCandidateGroup: boolean; + memberOfCandidateGroup: boolean; + memberOfCandidateUsers: boolean; involvedPeople: User []; parentTaskId: string; parentTaskName: string; @@ -57,37 +57,37 @@ export class TaskDetailsModel { 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; + constructor(obj?: any) { + this.id = obj && obj.id || null; + this.name = obj && obj.name || null; + this.priority = obj && obj.priority; + this.assignee = new User(obj.assignee); + this.adhocTaskCanBeReassigned = obj && obj.adhocTaskCanBeReassigned; + this.category = obj && obj.category || null; + this.created = obj && obj.created || null; + this.description = obj && obj.description || null; + this.dueDate = obj && obj.dueDate || null; + this.duration = obj && obj.duration || null; + this.endDate = obj && obj.endDate || null; + this.executionId = obj && obj.executionId || null; + this.formKey = obj && obj.formKey || null; + this.initiatorCanCompleteTask = obj && obj.initiatorCanCompleteTask || false; + this.managerOfCandidateGroup = obj && obj.managerOfCandidateGroup || false; + this.memberOfCandidateGroup = obj && obj.memberOfCandidateGroup || false; + this.memberOfCandidateUsers = obj && obj.memberOfCandidateUsers || false; + this.involvedPeople = obj && obj.involvedPeople; + this.parentTaskId = obj && obj.parentTaskId || null; + this.parentTaskName = obj && obj.parentTaskName || null; + this.processDefinitionCategory = obj && obj.processDefinitionCategory || null; + this.processDefinitionDeploymentId = obj && obj.processDefinitionDeploymentId || null; + this.processDefinitionDescription = obj && obj.processDefinitionDescription || null; + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.processDefinitionKey = obj && obj.processDefinitionKey || null; + this.processDefinitionName = obj && obj.processDefinitionName || null; + this.processDefinitionVersion = obj && obj.processDefinitionVersion || 0; + this.processInstanceId = obj && obj.processInstanceId || null; + this.processInstanceName = obj && obj.processInstanceName || null; + this.processInstanceStartUserId = obj && obj.processInstanceStartUserId || null; + this.taskDefinitionKey = obj && obj.taskDefinitionKey || null; } } diff --git a/ng2-components/ng2-activiti-tasklist/src/models/user.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/user.model.ts index 6d21ad4e0b..282c8240f4 100644 --- a/ng2-components/ng2-activiti-tasklist/src/models/user.model.ts +++ b/ng2-components/ng2-activiti-tasklist/src/models/user.model.ts @@ -29,10 +29,10 @@ export class User { 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; + constructor(obj?: any) { + this.id = obj && obj.id; + this.email = obj && obj.email || null; + this.firstName = obj && obj.firstName || null; + this.lastName = obj && obj.lastName || null; } } diff --git a/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.spec.ts b/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.spec.ts new file mode 100644 index 0000000000..85fbcf4619 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.spec.ts @@ -0,0 +1,169 @@ +/*! + * @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 { ReflectiveInjector } from '@angular/core'; +import { + AlfrescoAuthenticationService, + AlfrescoSettingsService, + AlfrescoApiService +} from 'ng2-alfresco-core'; +import { User } from '../models/user.model'; +import { ActivitiPeopleService } from './activiti-people.service'; + +declare let jasmine: any; + +const firstInvolvedUser: User = new User({ + id: '1', + email: 'fake-user1@fake.com', + firstName: 'fakeName1', + lastName: 'fakeLast1' +}); + +const secondInvolvedUser: User = new User({ + id: '2', + email: 'fake-user2@fake.com', + firstName: 'fakeName2', + lastName: 'fakeLast2' +}); + +const fakeInvolveUserList: User[] = [firstInvolvedUser, secondInvolvedUser]; + +describe('Activiti People Search Service', () => { + + let service, injector, apiService; + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + AlfrescoSettingsService, + AlfrescoApiService, + AlfrescoAuthenticationService, + ActivitiPeopleService + ]); + }); + + beforeEach(() => { + service = injector.get(ActivitiPeopleService); + apiService = injector.get(AlfrescoApiService); + }); + + it('can instantiate service with authorization', () => { + expect(apiService).not.toBeNull('authorization should be provided'); + let serviceApi = new ActivitiPeopleService(null, apiService); + + expect(serviceApi instanceof ActivitiPeopleService).toBe(true, 'new service should be ok'); + }); + + describe('when user is logged in', () => { + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('should be able to retrieve people to involve in the task', (done) => { + service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe( + (users: User[]) => { + expect(users).toBeDefined(); + expect(users.length).toBe(2); + expect(users[0].id).toEqual('1'); + expect(users[0].email).toEqual('fake-user1@fake.com'); + expect(users[0].firstName).toEqual('fakeName1'); + expect(users[0].lastName).toEqual('fakeLast1'); + done(); + }); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: {data: fakeInvolveUserList} + }); + }); + + it('should return empty list when there are no users to involve', (done) => { + service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe( + (users: User[]) => { + expect(users).toBeDefined(); + expect(users.length).toBe(0); + done(); + }); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: {} + }); + }); + + it('getWorkflowUsers catch errors call', (done) => { + service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(() => { + }, () => { + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 403 + }); + }); + + it('should be able to involve people in the task', (done) => { + service.involveUserWithTask('fake-task-id', 'fake-user-id').subscribe( + () => { + expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT'); + expect(jasmine.Ajax.requests.mostRecent().url).toContain('tasks/fake-task-id/action/involve'); + done(); + }); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200 + }); + }); + + it('involveUserWithTask catch errors call', (done) => { + service.involveUserWithTask('fake-task-id', 'fake-user-id').subscribe(() => { + }, () => { + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 403 + }); + }); + + it('should be able to remove involved people from task', (done) => { + service.removeInvolvedUser('fake-task-id', 'fake-user-id').subscribe( + () => { + expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT'); + expect(jasmine.Ajax.requests.mostRecent().url).toContain('tasks/fake-task-id/action/remove-involved'); + done(); + }); + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200 + }); + }); + + it('removeInvolvedUser catch errors call', (done) => { + service.removeInvolvedUser('fake-task-id', 'fake-user-id').subscribe(() => { + }, () => { + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 403 + }); + }); + }); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.ts b/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.ts new file mode 100644 index 0000000000..1e5e99433d --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.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 { Injectable } from '@angular/core'; +import { AlfrescoApiService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { Observable } from 'rxjs/Rx'; +import { Response } from '@angular/http'; +import { User } from '../models/user.model'; + +@Injectable() +export class ActivitiPeopleService { + + constructor(private authService: AlfrescoAuthenticationService, + private alfrescoJsApi: AlfrescoApiService) { + } + + getWorkflowUsers(taskId: string, searchWord: string): Observable { + let option = {excludeTaskId: taskId, filter: searchWord}; + return Observable.fromPromise(this.getWorkflowUserApi(option)) + .map((response: any) => response.data || []) + .catch(this.handleError); + } + + involveUserWithTask(taskId: string, idToInvolve: string): Observable { + let node = {userId: idToInvolve}; + return Observable.fromPromise(this.involveUserToTaskApi(taskId, node)) + .catch(this.handleError); + } + + removeInvolvedUser(taskId: string, idToRemove: string): Observable { + let node = {userId: idToRemove}; + return Observable.fromPromise(this.removeInvolvedUserFromTaskApi(taskId, node)) + .catch(this.handleError); + } + + private getWorkflowUserApi(options: any) { + return this.alfrescoJsApi.getInstance().activiti.usersWorkflowApi.getUsers(options); + } + + private involveUserToTaskApi(taskId: string, node: any) { + return this.alfrescoJsApi.getInstance().activiti.taskActionsApi.involveUser(taskId, node); + } + + private removeInvolvedUserFromTaskApi(taskId: string, node: any) { + return this.alfrescoJsApi.getInstance().activiti.taskActionsApi.removeInvolvedUser(taskId, node); + } + + /** + * Throw the error + * @param error + * @returns {ErrorObservable} + */ + private handleError(error: Response) { + // in a real world app, we may send the error to some remote logging infrastructure + // instead of just logging it to the console + console.error(error); + return Observable.throw(error || 'Server error'); + } +} 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 index 3c9eafefc8..b25120ce93 100644 --- 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 @@ -15,25 +15,35 @@ * 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'; +import { ReflectiveInjector } from '@angular/core'; +import { + AlfrescoAuthenticationService, + AlfrescoSettingsService, + AlfrescoApiService +} from 'ng2-alfresco-core'; +import { ActivitiTaskListService } from './activiti-tasklist.service'; +import { TaskDetailsModel } from '../models/task-details.model'; +import { FilterRepresentationModel, AppDefinitionRepresentationModel } from '../models/filter.model'; +import { Comment } from '../models/comment.model'; declare let AlfrescoApi: any; declare let jasmine: any; describe('ActivitiTaskListService', () => { - let service: any; + let fakeEmptyFilters = { + size: 0, total: 0, start: 0, + data: [ ] + }; 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'} - }, + new AppDefinitionRepresentationModel( + { + 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'} @@ -41,9 +51,32 @@ describe('ActivitiTaskListService', () => { ] }; + let fakeAppFilter = { + size: 1, total: 1, start: 0, + data: [ + { + id: 1, name: 'FakeInvolvedTasks', recent: false, icon: 'glyphicon-align-left', + filter: {sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved'} + } + ] + }; + + let fakeApps = { + size: 2, total: 2, start: 0, + data: [ + { + id: 1, defaultAppId: null, name: 'Sales-Fakes-App', description: 'desc-fake1', modelId: 22, + theme: 'theme-1-fake', icon: 'glyphicon-asterisk', 'deploymentId': '111', 'tenantId': null + }, + { + id: 2, defaultAppId: null, name: 'health-care-Fake', description: 'desc-fake2', modelId: 33, + theme: 'theme-2-fake', icon: 'glyphicon-asterisk', 'deploymentId': '444', 'tenantId': null + } + ] + }; + let fakeFilter = { - page: 2, filterId: 2, appDefinitionId: null, - filter: {sort: 'created-desc', name: '', state: 'open', assignment: 'fake-assignee'} + sort: 'created-desc', text: '', state: 'open', assignment: 'fake-assignee' }; let fakeUser = {id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName'}; @@ -93,18 +126,25 @@ describe('ActivitiTaskListService', () => { ] }; - beforeEachProviders(() => { - return [ - ActivitiTaskListService, - AlfrescoSettingsService, - AlfrescoAuthenticationService - ]; + let fakeAppPromise = new Promise(function (resolve, reject) { + resolve(fakeAppFilter); }); - beforeEach(inject([ActivitiTaskListService], (activitiTaskListService: ActivitiTaskListService) => { + let service, injector; + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + ActivitiTaskListService, + AlfrescoSettingsService, + AlfrescoApiService, + AlfrescoAuthenticationService + ]); + }); + + beforeEach(() => { + service = injector.get(ActivitiTaskListService); jasmine.Ajax.install(); - service = activitiTaskListService; - })); + }); afterEach(() => { jasmine.Ajax.uninstall(); @@ -128,9 +168,39 @@ describe('ActivitiTaskListService', () => { }); }); + it('should call the api withthe appId', (done) => { + spyOn(service, 'callApiTaskFilters').and.returnValue((fakeAppPromise)); + + let appId = 1; + service.getTaskListFilters(appId).subscribe( + (res) => { + expect(service.callApiTaskFilters).toHaveBeenCalledWith(appId); + done(); + } + ); + }); + + it('should return the app filter by id', (done) => { + let appId = 1; + service.getTaskListFilters(appId).subscribe( + (res) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(1); + expect(res[0].name).toEqual('FakeInvolvedTasks'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeAppFilter) + }); + }); + it('should return the task list filtered', (done) => { service.getTasks(fakeFilter).subscribe( - res => { + res => { expect(res).toBeDefined(); expect(res.size).toEqual(1); expect(res.total).toEqual(1); @@ -302,4 +372,145 @@ describe('ActivitiTaskListService', () => { }); }); + it('should return the total number of tasks', (done) => { + service.getTotalTasks(fakeFilter).subscribe( + res => { + expect(res).toBeDefined(); + expect(res.size).toEqual(1); + expect(res.total).toEqual(1); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeTaskList) + }); + }); + + it('should call the createDefaultFilter when the list is empty', (done) => { + spyOn(service, 'createDefaultFilter'); + + service.getTaskListFilters().subscribe( + (res) => { + expect(service.createDefaultFilter).toHaveBeenCalled(); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeEmptyFilters) + }); + }); + + it('should return the default filters', () => { + spyOn(service, 'addFilter'); + let filters = service.createDefaultFilter(); + expect(service.addFilter).toHaveBeenCalledTimes(4); + expect(filters).toBeDefined(); + expect(filters.length).toEqual(4); + }); + + it('should add a filter ', (done) => { + let filterFake = new FilterRepresentationModel({ + name: 'FakeNameFilter', + assignment: 'fake-assignement' + }); + + service.addFilter(filterFake).subscribe( + (res: FilterRepresentationModel) => { + expect(res).toBeDefined(); + expect(res.id).not.toEqual(''); + expect(res.name).toEqual('FakeNameFilter'); + expect(res.filter.assignment).toEqual('fake-assignement'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({ + id: '2233', name: 'FakeNameFilter', filter: {assignment: 'fake-assignement'} + }) + }); + }); + + it('should get the deployed apps ', (done) => { + service.getDeployedApplications().subscribe( + (res: any) => { + expect(res).toBeDefined(); + expect(res.length).toEqual(2); + expect(res[0].name).toEqual('Sales-Fakes-App'); + expect(res[0].description).toEqual('desc-fake1'); + expect(res[0].deploymentId).toEqual('111'); + expect(res[1].name).toEqual('health-care-Fake'); + expect(res[1].description).toEqual('desc-fake2'); + expect(res[1].deploymentId).toEqual('444'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeApps) + }); + }); + + it('should get the filter deployed app ', (done) => { + let name = 'health-care-Fake'; + service.getDeployedApplications(name).subscribe( + (res: any) => { + expect(res).toBeDefined(); + expect(res.name).toEqual('health-care-Fake'); + expect(res.description).toEqual('desc-fake2'); + expect(res.deploymentId).toEqual('444'); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeApps) + }); + }); + + it('should create a new standalone task ', (done) => { + let taskFake = new TaskDetailsModel({ + name: 'FakeNameTask', + description: 'FakeDescription', + category: '3' + }); + + service.createNewTask(taskFake).subscribe( + (res: TaskDetailsModel) => { + expect(res).toBeDefined(); + expect(res.id).not.toEqual(''); + expect(res.name).toEqual('FakeNameTask'); + expect(res.description).toEqual('FakeDescription'); + expect(res.category).toEqual('3'); + expect(res.created).not.toEqual(''); + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify({ + id: '777', + name: 'FakeNameTask', + description: 'FakeDescription', + category: '3', + assignee: fakeUser, + created: '2016-07-15T11:19:17.440+0000' + }) + }); + }); + }); 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 index 5b9fb5afb0..05411845f8 100644 --- a/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.ts +++ b/ng2-components/ng2-activiti-tasklist/src/services/activiti-tasklist.service.ts @@ -18,8 +18,8 @@ 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 {FilterRepresentationModel} from '../models/filter.model'; +import {TaskQueryRequestRepresentationModel} from '../models/filter.model'; import {Comment} from '../models/comment.model'; import {User} from '../models/user.model'; import {TaskDetailsModel} from '../models/task-details.model'; @@ -34,10 +34,15 @@ export class ActivitiTaskListService { * Retrive all the Deployed app * @returns {Observable} */ - getDeployedApplications(name: string): 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))); + .map((response: any) => { + if (name) { + return response.data.find(p => p.name === name); + } + return response.data; + }) + .catch(this.handleError); } /** @@ -47,23 +52,25 @@ export class ActivitiTaskListService { 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); + let filters: FilterRepresentationModel[] = []; + response.data.forEach((filter: FilterRepresentationModel) => { + let filterModel = new FilterRepresentationModel(filter); filters.push(filterModel); }); + if (response && response.data && response.data.length === 0) { + return this.createDefaultFilter(appId); + } return filters; }).catch(this.handleError); } /** * Retrive all the tasks filtered by filterModel - * @param filter - FilterModel + * @param filter - TaskFilterRepresentationModel * @returns {any} */ - getTasks(filter: FilterModel): Observable { - return Observable.fromPromise(this.callApiTasksFiltered(filter.filter)) + getTasks(requestNode: TaskQueryRequestRepresentationModel): Observable { + return Observable.fromPromise(this.callApiTasksFiltered(requestNode)) .map((res: any) => { return res; }).catch(this.handleError); @@ -93,8 +100,7 @@ export class ActivitiTaskListService { .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); + let user = new User(comment.createdBy); comments.push(new Comment(comment.id, comment.message, comment.created, user)); }); return comments; @@ -118,6 +124,33 @@ export class ActivitiTaskListService { }).catch(this.handleError); } + /** + * Create and return the default filters + * @param appId + * @returns {FilterRepresentationModel[]} + */ + createDefaultFilter(appId: string): FilterRepresentationModel[] { + let filters: FilterRepresentationModel[] = []; + + let involvedTasksFilter = this.getInvolvedTasksFilterInstance(appId); + this.addFilter(involvedTasksFilter); + filters.push(involvedTasksFilter); + + let myTasksFilter = this.getMyTasksFilterInstance(appId); + this.addFilter(myTasksFilter); + filters.push(myTasksFilter); + + let queuedTasksFilter = this.getQueuedTasksFilterInstance(appId); + this.addFilter(queuedTasksFilter); + filters.push(queuedTasksFilter); + + let completedTasksFilter = this.getCompletedTasksFilterInstance(appId); + this.addFilter(completedTasksFilter); + filters.push(completedTasksFilter); + + return filters; + } + /** * Add a task * @param task - TaskDetailsModel @@ -131,6 +164,19 @@ export class ActivitiTaskListService { }).catch(this.handleError); } + /** + * Add a filter + * @param filter - FilterRepresentationModel + * @returns {FilterRepresentationModel} + */ + addFilter(filter: FilterRepresentationModel): Observable { + return Observable.fromPromise(this.callApiAddFilter(filter)) + .map(res => res) + .map((response: FilterRepresentationModel) => { + return response; + }).catch(this.handleError); + } + /** * Add a comment to a task * @param id - taskId @@ -156,8 +202,34 @@ export class ActivitiTaskListService { .map(res => res); } - private callApiTasksFiltered(filter: FilterParamsModel) { - return this.authService.getAlfrescoApi().activiti.taskApi.listTasks(filter); + /** + * Return the total number of the tasks by filter + * @param requestNode - TaskFilterRepresentationModel + * @returns {any} + */ + public getTotalTasks(requestNode: TaskQueryRequestRepresentationModel): Observable { + requestNode.size = 0; + return Observable.fromPromise(this.callApiTasksFiltered(requestNode)) + .map((res: any) => { + return res; + }).catch(this.handleError); + } + + /** + * Create a new standalone task + * @param task - TaskDetailsModel + * @returns {TaskDetailsModel} + */ + createNewTask(task: TaskDetailsModel): Observable { + return Observable.fromPromise(this.callApiCreateTask(task)) + .map(res => res) + .map((response: TaskDetailsModel) => { + return new TaskDetailsModel(response); + }).catch(this.handleError); + } + + private callApiTasksFiltered(requestNode: TaskQueryRequestRepresentationModel) { + return this.authService.getAlfrescoApi().activiti.taskApi.listTasks(requestNode); } private callApiTaskFilters(appId?: string) { @@ -184,6 +256,10 @@ export class ActivitiTaskListService { return this.authService.getAlfrescoApi().activiti.taskApi.addSubtask(task.parentTaskId, task); } + private callApiAddFilter(filter: FilterRepresentationModel) { + return this.authService.getAlfrescoApi().activiti.userFiltersApi.createUserTaskFilter(filter); + } + private callApiTaskChecklist(id: string) { return this.authService.getAlfrescoApi().activiti.taskApi.getChecklist(id); } @@ -192,8 +268,72 @@ export class ActivitiTaskListService { return this.authService.getAlfrescoApi().activiti.taskApi.completeTask(id); } + private callApiCreateTask(task: TaskDetailsModel) { + return this.authService.getAlfrescoApi().activiti.taskApi.createNewTask(task); + } + private handleError(error: any) { console.error(error); return Observable.throw(error || 'Server error'); } + + /** + * Return a static Involved filter instance + * @param appId + * @returns {FilterRepresentationModel} + */ + getInvolvedTasksFilterInstance(appId: string): FilterRepresentationModel { + return new FilterRepresentationModel({ + 'name': 'Involved Tasks', + 'appId': appId, + 'recent': false, + 'icon': 'glyphicon-align-left', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'open', 'assignment': 'involved'} + }); + } + + /** + * Return a static My task filter instance + * @param appId + * @returns {FilterRepresentationModel} + */ + getMyTasksFilterInstance(appId: string): FilterRepresentationModel { + return new FilterRepresentationModel({ + 'name': 'My Tasks', + 'appId': appId, + 'recent': false, + 'icon': 'glyphicon-inbox', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'open', 'assignment': 'assignee'} + }); + } + + /** + * Return a static Queued filter instance + * @param appId + * @returns {FilterRepresentationModel} + */ + getQueuedTasksFilterInstance(appId: string): FilterRepresentationModel { + return new FilterRepresentationModel({ + 'name': 'Queued Tasks', + 'appId': appId, + 'recent': false, + 'icon': 'glyphicon-record', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'open', 'assignment': 'candidate'} + }); + } + + /** + * Return a static Completed filter instance + * @param appId + * @returns {FilterRepresentationModel} + */ + getCompletedTasksFilterInstance(appId: string): FilterRepresentationModel { + return new FilterRepresentationModel({ + 'name': 'Completed Tasks', + 'appId': appId, + 'recent': true, + 'icon': 'glyphicon-ok-sign', + 'filter': {'sort': 'created-desc', 'name': '', 'state': 'completed', 'assignment': 'involved'} + }); + } } diff --git a/ng2-components/ng2-activiti-tasklist/tsconfig.json b/ng2-components/ng2-activiti-tasklist/tsconfig.json index 39deca0930..7be35bfec8 100644 --- a/ng2-components/ng2-activiti-tasklist/tsconfig.json +++ b/ng2-components/ng2-activiti-tasklist/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es5", - "module": "system", + "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, @@ -15,13 +15,12 @@ "noImplicitReturns": false, "noImplicitUseStrict": false, "noFallthroughCasesInSwitch": true, - "outDir": "dist" + "outDir": "dist", + "types": ["core-js", "jasmine", "node"] }, "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 index 85e9df53c1..27e0dd81da 100644 --- a/ng2-components/ng2-activiti-tasklist/tslint.json +++ b/ng2-components/ng2-activiti-tasklist/tslint.json @@ -35,7 +35,7 @@ "no-arg": true, "no-bitwise": false, "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, + "no-consecutive-blank-lines": true, "no-console": [ true, "debug", diff --git a/ng2-components/ng2-activiti-tasklist/typings.json b/ng2-components/ng2-activiti-tasklist/typings.json deleted file mode 100644 index d8954c2485..0000000000 --- a/ng2-components/ng2-activiti-tasklist/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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/.npmignore b/ng2-components/ng2-alfresco-core/.npmignore index a38d07049e..85ed1df8ce 100644 --- a/ng2-components/ng2-alfresco-core/.npmignore +++ b/ng2-components/ng2-alfresco-core/.npmignore @@ -1,7 +1,6 @@ npm-debug.log .idea -assets/ coverage/ demo/ node_modules diff --git a/ng2-components/ng2-alfresco-core/README.md b/ng2-components/ng2-alfresco-core/README.md index 2e5ece5e67..459deca8b3 100644 --- a/ng2-components/ng2-alfresco-core/README.md +++ b/ng2-components/ng2-alfresco-core/README.md @@ -1,4 +1,4 @@ -# Alfresco Angular2 Components core +# Alfresco Angular 2 Components core

@@ -32,12 +32,9 @@

-Core library for other ng2-alfresco components. -This should be added as a dependency for any project using the components. - ## Prerequisites -Before you start using this development framework, make sure you have installed all required software and done all the +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 @@ -51,6 +48,51 @@ npm install --save ng2-alfresco-core ### Components - Context Menu directive +- Material Design directives + - [mdl] + - [alfresco-mdl-button] + - [alfresco-mdl-menu] + - [alfresco-mdl-tabs] + +### Services + +- **AlfrescoApiService**, provides access to Alfresco JS API instance +- **AlfrescoAuthenticationService**, main authentication APIs +- **AlfrescoTranslationService**, various i18n-related APIs +- **ContextMenuService**, global context menu APIs + + +#### Alfresco Api Service + +Provides access to initialized **AlfrescoJSApi** instance. + +```ts + +export class MyComponent implements OnInit { + + constructor(private apiService: AlfrescoApiService) { + } + + ngOnInit() { + let nodeId = 'some-node-id'; + let params = {}; + this.getAlfrescoApi().nodes + .getNodeChildren(nodeId, params) + .then(result => console.log(result)); + } +} +``` + +**Note for developers**: _the TypeScript declaration files for Alfresco JS API +are still under development and some Alfresco APIs may not be accessed +via your favourite IDE's intellisense or TypeScript compiler. +In case of any TypeScript type check errors you can still call any supported +Alfresco JS api by casting the instance to `any` type like the following:_ + +```ts +let apiService: any = this.authService.getAlfrescoApi(); +apiService.nodes.addNode('-root-', body, {}); +``` #### Context Menu directive @@ -88,49 +130,49 @@ export class MyComponent implements OnInit { } ``` -### Services - -- Authentication Service -- 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'; +```ts +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core'; @Component({ - selector: 'my-app', + selector: 'alfresco-app-demo', template: `
Authentication failed to ip {{ ecmHost }} with user: admin, admin
- Authentication successfull to ip {{ ecmHost }} with user: admin, admin, your token is {{ token }} +
ECM
+ Authentication successfull to ip {{ ecmHost }} with user: admin, admin
+ your token is {{ tokenEcm }}
+
BPM
+ Authentication successfull to ip {{ bpmHost }} with user: admin, admin
+ your token is {{ tokenBpm }}
` }) class MyDemoApp { authenticated: boolean = false; - ecmHost: string = 'http://127.0.0.1:8080'; + public ecmHost: string = 'http://localhost:8080'; - token: string; + public bpmHost: string = 'http://localhost:9999'; + + tokenBpm: string; + + tokenEcm: string; constructor(public alfrescoAuthenticationService: AlfrescoAuthenticationService, private alfrescoSettingsService: AlfrescoSettingsService) { alfrescoSettingsService.ecmHost = this.ecmHost; - alfrescoSettingsService.setProviders('ECM'); + alfrescoSettingsService.bpmHost = this.bpmHost; + + alfrescoSettingsService.setProviders('ALL'); } ngOnInit() { @@ -140,7 +182,8 @@ class MyDemoApp { login() { this.alfrescoAuthenticationService.login('admin', 'admin').subscribe( token => { - this.token = token.ticket; + this.tokenBpm = this.alfrescoAuthenticationService.getTicketBpm(); + this.tokenEcm = this.alfrescoAuthenticationService.getTicketEcm(); this.authenticated = true; }, error => { @@ -149,18 +192,42 @@ class MyDemoApp { }); } } -bootstrap(MyDemoApp, [ - HTTP_PROVIDERS, - ALFRESCO_CORE_PROVIDERS -]); +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot() + ], + declarations: [MyDemoApp], + bootstrap: [MyDemoApp] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); ``` +#### Renditions Service + +* getRenditionsListByNodeId(nodeId: string) +* createRendition(nodeId: string, encoding: string) +* getRendition(nodeId: string, encoding: string) +* isRenditionAvailable(nodeId: string, encoding: string) ## Build from sources Alternatively you can build component from sources with the following commands: +```sh +npm install +npm run build +``` + +## Build from sources + +Alternatively you can build component from sources with the following commands: + + ```sh npm install npm run build @@ -172,7 +239,7 @@ npm run build $ npm run build:w ``` -### Running unit tests +## Running unit tests ```sh npm test @@ -184,11 +251,25 @@ npm test npm test-browser ``` -This task rebuilds all the code, runs tslint, license checks and other quality check tools +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 -``` \ No newline at end of file +``` + +## NPM scripts + +| Command | Description | +| --- | --- | +| npm run build | Build component | +| npm run build:w | Build component and keep watching the changes | +| npm run test | Run unit tests in the console | +| npm run test-browser | Run unit tests in the browser +| npm run coverage | Run unit tests and display code coverage report | + +## License + +[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE) \ No newline at end of file diff --git a/ng2-components/ng2-alfresco-core/index.ts b/ng2-components/ng2-alfresco-core/index.ts index 231df2fe5b..c55723ef05 100644 --- a/ng2-components/ng2-alfresco-core/index.ts +++ b/ng2-components/ng2-alfresco-core/index.ts @@ -15,28 +15,76 @@ * limitations under the License. */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { HttpModule, Http } from '@angular/http'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule, TranslateLoader } from 'ng2-translate/ng2-translate'; + import { + AlfrescoApiService, AlfrescoSettingsService, AlfrescoTranslationLoader, AlfrescoTranslationService, - AlfrescoPipeTranslate, AlfrescoAuthenticationService, - AlfrescoContentService + AlfrescoContentService, + RenditionsService } from './src/services/index'; -import { ContextMenuService } from './src/components/context-menu/context-menu.service'; +import { MATERIAL_DESIGN_DIRECTIVES } from './src/components/material/index'; +import { CONTEXT_MENU_PROVIDERS, CONTEXT_MENU_DIRECTIVES } from './src/components/context-menu/index'; export * from './src/services/index'; export * from './src/components/index'; export * from './src/utils/index'; -export const ALFRESCO_CORE_PROVIDERS: [any] = [ +export const ALFRESCO_CORE_PROVIDERS: any[] = [ + AlfrescoApiService, AlfrescoAuthenticationService, AlfrescoContentService, AlfrescoSettingsService, AlfrescoTranslationLoader, AlfrescoTranslationService, - AlfrescoPipeTranslate, - ContextMenuService + RenditionsService, + ...CONTEXT_MENU_PROVIDERS ]; +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + HttpModule, + TranslateModule.forRoot({ + provide: TranslateLoader, + useFactory: (http) => new AlfrescoTranslationLoader(http), + deps: [Http] + }) + ], + declarations: [ + ...MATERIAL_DESIGN_DIRECTIVES, + ...CONTEXT_MENU_DIRECTIVES + ], + providers: [ + ...ALFRESCO_CORE_PROVIDERS + ], + exports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + HttpModule, + TranslateModule, + ...MATERIAL_DESIGN_DIRECTIVES, + ...CONTEXT_MENU_DIRECTIVES + ] +}) +export class CoreModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: CoreModule, + providers: [ + ...ALFRESCO_CORE_PROVIDERS + ] + }; + } +} diff --git a/ng2-components/ng2-alfresco-core/karma-test-shim.js b/ng2-components/ng2-alfresco-core/karma-test-shim.js index 3a95ee82c7..e235fb5b81 100644 --- a/ng2-components/ng2-alfresco-core/karma-test-shim.js +++ b/ng2-components/ng2-alfresco-core/karma-test-shim.js @@ -5,102 +5,98 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; __karma__.loaded = function() {}; +var builtPath = '/base/dist/'; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +var paths = { + // paths serve as alias + 'npm:': 'base/node_modules/' +}; + var map = { 'app': 'base/dist', - 'rxjs': 'base/node_modules/rxjs', - '@angular': 'base/node_modules/@angular', - 'ng2-translate' : '/base/node_modules/ng2-translate' + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // testing + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + + 'alfresco-js-api': 'npm:alfresco-js-api/dist' }; var packages = { 'app': { main: 'main.js', defaultExtension: 'js' }, 'rxjs': { defaultExtension: 'js' }, - 'alfresco-js-api' : { main: 'alfresco-js-api.js', defaultExtension: 'js' }, - 'ng2-translate': { 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, {}) + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'} }; var config = { + paths: paths, 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') - ]) - }) +System.import('@angular/core/testing') + .then(initTestBed) + .then(initTesting); + +function initTestBed(){ + 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); + var coreTesting = providers[0]; + var browserTesting = providers[1]; + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); }) - .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') +// Import all spec files and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { return System.import(moduleName); - }); + }) + ) + .then(__karma__.start, __karma__.error); } diff --git a/ng2-components/ng2-alfresco-core/karma.conf.js b/ng2-components/ng2-alfresco-core/karma.conf.js index 92702df655..b92800c9ef 100644 --- a/ng2-components/ng2-alfresco-core/karma.conf.js +++ b/ng2-components/ng2-alfresco-core/karma.conf.js @@ -7,19 +7,36 @@ module.exports = function (config) { 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}, + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', - {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, - {pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false}, + // Polyfills + 'node_modules/core-js/client/shim.js', + 'node_modules/reflect-metadata/Reflect.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, + { pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, + + // Paths loaded via module imports: + // Angular itself {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: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, - {pattern: 'karma-test-shim.js', included: true, watched: true}, + 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/ng2-translate/**/*.js.map', included: false, watched: false}, + + 'karma-test-shim.js', // paths loaded via module imports {pattern: 'dist/**/*.js', included: false, watched: true}, @@ -71,15 +88,16 @@ module.exports = function (config) { ], // Coverage reporter generates the coverage - reporters: ['mocha', 'coverage', 'coveralls', 'kjhtml'], + 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'] + 'dist/**/!(*spec|index|*mock|*model|mdl*).js': 'coverage' }, coverageReporter: { + includeAllSources: true, dir: 'coverage/', subdir: 'report', reporters: [ @@ -89,7 +107,7 @@ module.exports = function (config) { {type: 'lcov'} ] } - } + }; if (process.env.TRAVIS) { configuration.browsers = ['Chrome_travis_ci']; diff --git a/ng2-components/ng2-alfresco-core/package.json b/ng2-components/ng2-alfresco-core/package.json index e884ab47ca..4f93d589db 100644 --- a/ng2-components/ng2-alfresco-core/package.json +++ b/ng2-components/ng2-alfresco-core/package.json @@ -1,12 +1,10 @@ { "name": "ng2-alfresco-core", "description": "Alfresco Angular 2 Components core", - "version": "0.3.0", + "version": "0.4.0", "author": "Alfresco Software, Ltd.", "scripts": { - "postinstall": "typings install", - "clean": "rimraf dist node_modules typings", - "typings": "typings install", + "clean": "npm install rimraf && rimraf dist node_modules typings", "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\"", @@ -37,7 +35,7 @@ "contributors": [ { "name": "Denys Vuika", - "email": "denis.vuyka@gmail.com" + "email": "denys.vuika@gmail.com" }, { "name": "Mario Romano", @@ -55,49 +53,50 @@ "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", - "@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", + "@angular/router": "3.0.0", + "@angular/upgrade": "2.0.0", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", "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" - }, - "peerDependencies": { - "material-design-icons": "^2.2.3", - "material-design-lite": "^1.1.3" + "zone.js": "^0.6.23", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1" }, "devDependencies": { - "coveralls": "^2.11.9", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", "cpx": "1.3.1", "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.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" + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" }, "license-check-config": { "src": [ diff --git a/ng2-components/ng2-alfresco-core/src/assets/AlfrescoApi.mock.ts b/ng2-components/ng2-alfresco-core/src/assets/AlfrescoApi.mock.ts index 3835e67611..85e93b30ba 100644 --- a/ng2-components/ng2-alfresco-core/src/assets/AlfrescoApi.mock.ts +++ b/ng2-components/ng2-alfresco-core/src/assets/AlfrescoApi.mock.ts @@ -15,7 +15,6 @@ * limitations under the License. */ - export class AlfrescoApiMock { login(username: string, password: string) { diff --git a/ng2-components/ng2-alfresco-core/src/assets/renditionsService.mock.ts b/ng2-components/ng2-alfresco-core/src/assets/renditionsService.mock.ts new file mode 100644 index 0000000000..91c7d1148a --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/assets/renditionsService.mock.ts @@ -0,0 +1,81 @@ +/*! + * @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 var fakeRedition = { + 'entry': { + 'id': 'pdf', + 'content': {'mimeType': 'application/pdf', 'mimeTypeName': 'Adobe PDF Document'}, + 'status': 'NOT_CREATED' + } +}; + +export var fakeReditionCreated = { + 'entry': { + 'id': 'pdf', + 'content': {'mimeType': 'application/pdf', 'mimeTypeName': 'Adobe PDF Document'}, + 'status': 'CREATED' + } +}; + +export var fakeReditionsList = { + 'list': { + 'pagination': { + 'count': 6, + 'hasMoreItems': false, + 'totalItems': 6, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [{ + 'entry': { + 'id': 'avatar', + 'content': {'mimeType': 'image/png', 'mimeTypeName': 'PNG Image'}, + 'status': 'NOT_CREATED' + } + }, { + 'entry': { + 'id': 'avatar32', + 'content': {'mimeType': 'image/png', 'mimeTypeName': 'PNG Image'}, + 'status': 'NOT_CREATED' + } + }, { + 'entry': { + 'id': 'doclib', + 'content': {'mimeType': 'image/png', 'mimeTypeName': 'PNG Image'}, + 'status': 'NOT_CREATED' + } + }, { + 'entry': { + 'id': 'imgpreview', + 'content': {'mimeType': 'image/jpeg', 'mimeTypeName': 'JPEG Image'}, + 'status': 'NOT_CREATED' + } + }, { + 'entry': { + 'id': 'medium', + 'content': {'mimeType': 'image/jpeg', 'mimeTypeName': 'JPEG Image'}, + 'status': 'NOT_CREATED' + } + }, { + 'entry': { + 'id': 'pdf', + 'content': {'mimeType': 'application/pdf', 'mimeTypeName': 'Adobe PDF Document'}, + 'status': 'NOT_CREATED' + } + }] + } +}; diff --git a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.spec.ts b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.spec.ts index 5ac723cc3d..ee6cf3e744 100644 --- a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.spec.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { describe, it, beforeEach } from '@angular/core/testing'; import { ContextMenuService } from './context-menu.service'; import { ContextMenuHolderComponent } from './context-menu-holder.component'; diff --git a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.ts b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.ts index 9f406ed117..59792c47af 100644 --- a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.ts +++ b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu-holder.component.ts @@ -35,14 +35,11 @@ import { ContextMenuService } from './context-menu.service'; .context-menu { list-style-type: none; position: static; - - height: auto; width: auto; min-width: 124px; padding: 8px 0; margin: 0; - box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); border-radius: 2px; } diff --git a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.directive.spec.ts b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.directive.spec.ts index b8b7b169f1..32d9a6902b 100644 --- a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.directive.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.directive.spec.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { describe, it, beforeEach } from '@angular/core/testing'; import { ContextMenuDirective } from './context-menu.directive'; import { ContextMenuService } from './context-menu.service'; diff --git a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.service.spec.ts b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.service.spec.ts index 1ebdcede74..a98fd3c5c8 100644 --- a/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/components/context-menu/context-menu.service.spec.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { describe, it, beforeEach } from '@angular/core/testing'; import { ContextMenuService } from './context-menu.service'; describe('ContextMenuService', () => { diff --git a/ng2-components/ng2-alfresco-core/src/components/material/index.ts b/ng2-components/ng2-alfresco-core/src/components/material/index.ts index 8760c82be1..12bc3fab51 100644 --- a/ng2-components/ng2-alfresco-core/src/components/material/index.ts +++ b/ng2-components/ng2-alfresco-core/src/components/material/index.ts @@ -15,19 +15,22 @@ * limitations under the License. */ -import { MDL } from './MaterialDesignLiteUpgradeElement'; +import { MDL } from './mdl-upgrade-element.directive'; import { AlfrescoMdlButtonDirective } from './mdl-button.directive'; import { AlfrescoMdlMenuDirective } from './mdl-menu.directive'; import { AlfrescoMdlTabsDirective } from './mdl-tabs.directive'; +import { AlfrescoMdlTextFieldDirective } from './mdl-textfield.directive'; -export * from './MaterialDesignLiteUpgradeElement'; +export * from './mdl-upgrade-element.directive'; export * from './mdl-button.directive'; export * from './mdl-menu.directive'; export * from './mdl-tabs.directive'; +export * from './mdl-textfield.directive'; export const MATERIAL_DESIGN_DIRECTIVES: [any] = [ MDL, AlfrescoMdlButtonDirective, AlfrescoMdlMenuDirective, - AlfrescoMdlTabsDirective + AlfrescoMdlTabsDirective, + AlfrescoMdlTextFieldDirective ]; diff --git a/ng2-components/ng2-alfresco-core/src/components/material/mdl-textfield.directive.ts b/ng2-components/ng2-alfresco-core/src/components/material/mdl-textfield.directive.ts new file mode 100644 index 0000000000..d8e0a1be0b --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/components/material/mdl-textfield.directive.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. + */ + +import { Directive, ElementRef, AfterViewInit } from '@angular/core'; + +declare var componentHandler; + +@Directive({ + selector: '[alfresco-mdl-textfield]' +}) +export class AlfrescoMdlTextFieldDirective implements AfterViewInit { + + constructor(private element: ElementRef) {} + + ngAfterViewInit() { + if (componentHandler) { + let el = this.element.nativeElement; + el.classList.add('mdl-textfield'); + el.classList.add('mdl-js-textfield'); + el.classList.add('mdl-textfield--floating-label'); + componentHandler.upgradeElement(el, 'MaterialTextfield'); + } + } +} diff --git a/ng2-components/ng2-alfresco-core/src/components/material/MaterialDesignLiteUpgradeElement.ts b/ng2-components/ng2-alfresco-core/src/components/material/mdl-upgrade-element.directive.ts similarity index 100% rename from ng2-components/ng2-alfresco-core/src/components/material/MaterialDesignLiteUpgradeElement.ts rename to ng2-components/ng2-alfresco-core/src/components/material/mdl-upgrade-element.directive.ts diff --git a/demo-shell-ng2/app/services/notification.service.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoApi.service.ts similarity index 71% rename from demo-shell-ng2/app/services/notification.service.ts rename to ng2-components/ng2-alfresco-core/src/services/AlfrescoApi.service.ts index d93a12144b..dad5f6d8e9 100644 --- a/demo-shell-ng2/app/services/notification.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoApi.service.ts @@ -16,16 +16,19 @@ */ import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs/Subject'; +import { AlfrescoApi } from 'alfresco-js-api'; @Injectable() -export class NotificationService { +export class AlfrescoApiService { - notificationsdSource = new Subject(); + private _instance: AlfrescoApi; - notifications = this.notificationsdSource.asObservable(); - - sendNotification(message: string) { - this.notificationsdSource.next(message); + public getInstance(): AlfrescoApi { + return this._instance; } + + public setInstance(value: AlfrescoApi) { + this._instance = value; + } + } diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.spec.ts index 34e9ccf2f6..e610ad7c9d 100644 --- a/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.spec.ts @@ -15,23 +15,28 @@ * limitations under the License. */ -import {it, describe, beforeEach, afterEach} from '@angular/core/testing'; -import {ReflectiveInjector, provide} from '@angular/core'; -import {AlfrescoSettingsService} from './AlfrescoSettings.service'; -import {AlfrescoAuthenticationService} from './AlfrescoAuthentication.service'; +import { ReflectiveInjector } from '@angular/core'; +import { AlfrescoSettingsService } from './AlfrescoSettings.service'; +import { AlfrescoAuthenticationService } from './AlfrescoAuthentication.service'; +import { AlfrescoApiService } from './AlfrescoApi.service'; -declare var AlfrescoApi: any; declare let jasmine: any; describe('AlfrescoAuthentication', () => { - let injector, authService; + let injector; + let authService: AlfrescoAuthenticationService; + let settingsService: AlfrescoSettingsService; beforeEach(() => { injector = ReflectiveInjector.resolveAndCreate([ - provide(AlfrescoSettingsService, {useClass: AlfrescoSettingsService}), + AlfrescoSettingsService, + AlfrescoApiService, AlfrescoAuthenticationService ]); + authService = injector.get(AlfrescoAuthenticationService); + settingsService = injector.get(AlfrescoSettingsService); + let store = {}; spyOn(localStorage, 'getItem').and.callFake(function (key) { @@ -61,14 +66,29 @@ describe('AlfrescoAuthentication', () => { describe('when the setting is ECM', () => { beforeEach(() => { - authService = injector.get(AlfrescoAuthenticationService); - authService.alfrescoSetting.setProviders('ECM'); + settingsService.setProviders('ECM'); }); it('should return an ECM ticket after the login done', (done) => { authService.login('fake-username', 'fake-password').subscribe(() => { expect(authService.isLoggedIn()).toBe(true); expect(authService.getTicketEcm()).toEqual('fake-post-ticket'); + expect(authService.isEcmLoggedIn()).toBe(true); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 201, + contentType: 'application/json', + responseText: JSON.stringify({'entry': {'id': 'fake-post-ticket', 'userId': 'admin'}}) + }); + }); + + it('should save only ECM ticket on localStorage', (done) => { + authService.login('fake-username', 'fake-password').subscribe(() => { + expect(authService.isLoggedIn()).toBe(true); + expect(authService.getTicketBpm()).toBeNull(); + expect(authService.getAlfrescoApi().bpmAuth.isLoggedIn()).toBeFalsy(); done(); }); @@ -86,6 +106,7 @@ describe('AlfrescoAuthentication', () => { (err: any) => { expect(authService.isLoggedIn()).toBe(false); expect(authService.getTicketEcm()).toBe(null); + expect(authService.isEcmLoggedIn()).toBe(false); done(); }); @@ -121,6 +142,7 @@ describe('AlfrescoAuthentication', () => { authService.logout().subscribe(() => { expect(authService.isLoggedIn()).toBe(false); expect(authService.getTicketEcm()).toBe(null); + expect(authService.isEcmLoggedIn()).toBe(false); done(); }); @@ -138,20 +160,21 @@ describe('AlfrescoAuthentication', () => { it('should return false if the user is not logged in', () => { expect(authService.isLoggedIn()).toBe(false); + expect(authService.isEcmLoggedIn()).toBe(false); }); }); describe('when the setting is BPM', () => { beforeEach(() => { - authService = injector.get(AlfrescoAuthenticationService); - authService.alfrescoSetting.setProviders('BPM'); + settingsService.setProviders('BPM'); }); it('should return an BPM ticket after the login done', (done) => { authService.login('fake-username', 'fake-password').subscribe(() => { expect(authService.isLoggedIn()).toBe(true); expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk'); + expect(authService.isBpmLoggedIn()).toBe(true); done(); }); @@ -160,6 +183,21 @@ describe('AlfrescoAuthentication', () => { }); }); + it('should save only BPM ticket on localStorage', (done) => { + authService.login('fake-username', 'fake-password').subscribe(() => { + expect(authService.isLoggedIn()).toBe(true); + expect(authService.getTicketEcm()).toBeNull(); + expect(authService.getAlfrescoApi().ecmAuth.isLoggedIn()).toBeFalsy(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 201, + contentType: 'application/json', + responseText: JSON.stringify({'entry': {'id': 'fake-post-ticket', 'userId': 'admin'}}) + }); + }); + it('should return ticket undefined when the credentials are wrong', (done) => { authService.login('fake-wrong-username', 'fake-wrong-password').subscribe( (res) => { @@ -167,6 +205,7 @@ describe('AlfrescoAuthentication', () => { (err: any) => { expect(authService.isLoggedIn()).toBe(false); expect(authService.getTicketBpm()).toBe(null); + expect(authService.isBpmLoggedIn()).toBe(false); done(); }); @@ -180,6 +219,7 @@ describe('AlfrescoAuthentication', () => { authService.logout().subscribe(() => { expect(authService.isLoggedIn()).toBe(false); expect(authService.getTicketBpm()).toBe(null); + expect(authService.isBpmLoggedIn()).toBe(false); done(); }); @@ -212,24 +252,23 @@ describe('AlfrescoAuthentication', () => { describe('Setting service change should reflect in the api', () => { beforeEach(() => { - authService = injector.get(AlfrescoAuthenticationService); - authService.alfrescoSetting.setProviders('ALL'); + settingsService.setProviders('ALL'); }); it('should host ecm url change be reflected in the api configuration', () => { - authService.alfrescoSetting.ecmHost = '127.99.99.99'; + settingsService.ecmHost = '127.99.99.99'; expect(authService.getAlfrescoApi().config.hostEcm).toBe('127.99.99.99'); }); it('should host bpm url change be reflected in the api configuration', () => { - authService.alfrescoSetting.bpmHost = '127.99.99.99'; + settingsService.bpmHost = '127.99.99.99'; expect(authService.getAlfrescoApi().config.hostBpm).toBe('127.99.99.99'); }); it('should host bpm provider change be reflected in the api configuration', () => { - authService.alfrescoSetting.setProviders('ECM'); + settingsService.setProviders('ECM'); expect(authService.getAlfrescoApi().config.provider).toBe('ECM'); }); @@ -239,8 +278,7 @@ describe('AlfrescoAuthentication', () => { describe('when the setting is both ECM and BPM ', () => { beforeEach(() => { - authService = injector.get(AlfrescoAuthenticationService); - authService.providers = 'ALL'; + settingsService.setProviders('ALL'); }); it('should return both ECM and BPM tickets after the login done', (done) => { @@ -248,6 +286,8 @@ describe('AlfrescoAuthentication', () => { expect(authService.isLoggedIn()).toBe(true); expect(authService.getTicketEcm()).toEqual('fake-post-ticket'); expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk'); + expect(authService.isBpmLoggedIn()).toBe(true); + expect(authService.isEcmLoggedIn()).toBe(true); done(); }); @@ -270,6 +310,7 @@ describe('AlfrescoAuthentication', () => { expect(authService.isLoggedIn()).toBe(false); expect(authService.getTicketEcm()).toBe(null); expect(authService.getTicketBpm()).toBe(null); + expect(authService.isEcmLoggedIn()).toBe(false); done(); }); @@ -290,6 +331,7 @@ describe('AlfrescoAuthentication', () => { expect(authService.isLoggedIn()).toBe(false); expect(authService.getTicketEcm()).toBe(null); expect(authService.getTicketBpm()).toBe(null); + expect(authService.isBpmLoggedIn()).toBe(false); done(); }); @@ -312,6 +354,8 @@ describe('AlfrescoAuthentication', () => { expect(authService.isLoggedIn()).toBe(false); expect(authService.getTicketEcm()).toBe(null); expect(authService.getTicketBpm()).toBe(null); + expect(authService.isBpmLoggedIn()).toBe(false); + expect(authService.isEcmLoggedIn()).toBe(false); done(); }); diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.ts index ba92bff800..c91adc4258 100644 --- a/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoAuthentication.service.ts @@ -15,11 +15,13 @@ * limitations under the License. */ -import {Injectable} from '@angular/core'; -import {Observable} from 'rxjs/Rx'; -import {AlfrescoSettingsService} from './AlfrescoSettings.service'; - -declare let AlfrescoApi: any; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { AlfrescoSettingsService } from './AlfrescoSettings.service'; +import { AlfrescoApiService } from './AlfrescoApi.service'; +import * as alfrescoApi from 'alfresco-js-api'; +import { AlfrescoApi } from 'alfresco-js-api'; +import { Subject } from 'rxjs/Subject'; /** * The AlfrescoAuthenticationService provide the login service and store the ticket in the localStorage @@ -27,32 +29,46 @@ declare let AlfrescoApi: any; @Injectable() export class AlfrescoAuthenticationService { - alfrescoApi: any; + alfrescoApi: AlfrescoApi; - /**A + public loginSubject: Subject = new Subject(); + + public logoutSubject: Subject = new Subject(); + + /** * Constructor - * @param alfrescoSetting + * @param settingsService + * @param apiService */ - constructor(public alfrescoSetting: AlfrescoSettingsService) { - this.alfrescoApi = new AlfrescoApi({ - provider: this.alfrescoSetting.getProviders(), + constructor(private settingsService: AlfrescoSettingsService, + private apiService: AlfrescoApiService) { + this.alfrescoApi = new alfrescoApi({ + provider: this.settingsService.getProviders(), ticketEcm: this.getTicketEcm(), ticketBpm: this.getTicketBpm(), - hostEcm: this.alfrescoSetting.ecmHost, - hostBpm: this.alfrescoSetting.bpmHost + hostEcm: this.settingsService.ecmHost, + hostBpm: this.settingsService.bpmHost, + contextRoot: 'alfresco', + disableCsrf: true }); - alfrescoSetting.bpmHostSubject.subscribe((bpmHost) => { + settingsService.bpmHostSubject.subscribe((bpmHost) => { this.alfrescoApi.changeBpmHost(bpmHost); }); - alfrescoSetting.ecmHostSubject.subscribe((ecmHost) => { + settingsService.ecmHostSubject.subscribe((ecmHost) => { this.alfrescoApi.changeEcmHost(ecmHost); }); - alfrescoSetting.providerSubject.subscribe((value) => { + settingsService.csrfSubject.subscribe((csrf) => { + this.alfrescoApi.changeCsrfConfig(csrf); + }); + + settingsService.providerSubject.subscribe((value) => { this.alfrescoApi.config.provider = value; }); + + this.apiService.setInstance(this.alfrescoApi); } /** @@ -69,11 +85,13 @@ export class AlfrescoAuthenticationService { * @param password * @returns {Observable|Observable} */ - login(username: string, password: string) { + login(username: string, password: string): Observable<{ type: string, ticket: any }> { + this.removeTicket(); return Observable.fromPromise(this.callApiLogin(username, password)) .map((response: any) => { this.saveTickets(); - return {type: this.alfrescoSetting.getProviders(), ticket: response}; + this.loginSubject.next(response); + return {type: this.settingsService.getProviders(), ticket: response}; }) .catch(this.handleError); } @@ -94,10 +112,11 @@ export class AlfrescoAuthenticationService { * @returns {Observable|Observable} */ public logout() { + this.removeTicket(); return Observable.fromPromise(this.callApiLogout()) .map(res => res) .do(response => { - this.removeTicket(); + this.logoutSubject.next(response); return response; }) .catch(this.handleError); @@ -165,7 +184,7 @@ export class AlfrescoAuthenticationService { * The method save the ECM ticket in the localStorage */ public saveTicketEcm(): void { - if (this.alfrescoApi) { + if (this.alfrescoApi && this.alfrescoApi.getTicketEcm()) { localStorage.setItem('ticket-ECM', this.alfrescoApi.getTicketEcm()); } } @@ -174,11 +193,25 @@ export class AlfrescoAuthenticationService { * The method save the BPM ticket in the localStorage */ public saveTicketBpm(): void { - if (this.alfrescoApi) { + if (this.alfrescoApi && this.alfrescoApi.getTicketBpm()) { localStorage.setItem('ticket-BPM', this.alfrescoApi.getTicketBpm()); } } + /** + * The method return true if user is logged in on ecm provider + */ + public isEcmLoggedIn() { + return this.alfrescoApi.ecmAuth && !!this.alfrescoApi.ecmAuth.isLoggedIn(); + } + + /** + * The method return true if user is logged in on bpm provider + */ + public isBpmLoggedIn() { + return this.alfrescoApi.bpmAuth && !!this.alfrescoApi.bpmAuth.isLoggedIn(); + } + /** * The method write the error in the console browser * @param error diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoContent.spec.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoContent.spec.ts index 922f976d63..2f8e3a9b8d 100644 --- a/ng2-components/ng2-alfresco-core/src/services/AlfrescoContent.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoContent.spec.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import {describe, it, beforeEach} from '@angular/core/testing'; import {ReflectiveInjector} from '@angular/core'; import {AlfrescoSettingsService} from './AlfrescoSettings.service'; import {AlfrescoAuthenticationService} from './AlfrescoAuthentication.service'; import {AlfrescoContentService} from './AlfrescoContent.service'; +import { AlfrescoApiService } from './AlfrescoApi.service'; describe('AlfrescoContentService', () => { @@ -29,6 +29,7 @@ describe('AlfrescoContentService', () => { beforeEach(() => { injector = ReflectiveInjector.resolveAndCreate([ + AlfrescoApiService, AlfrescoContentService, AlfrescoAuthenticationService, AlfrescoSettingsService diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.service.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.service.ts index 012e35c3a2..55a46de707 100644 --- a/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.service.ts @@ -23,11 +23,13 @@ export class AlfrescoSettingsService { static DEFAULT_ECM_ADDRESS: string = 'http://' + window.location.hostname + ':8080'; static DEFAULT_BPM_ADDRESS: string = 'http://' + window.location.hostname + ':9999'; + static DEFAULT_CSRF_CONFIG: boolean = false; static DEFAULT_BPM_CONTEXT_PATH: string = '/activiti-app'; private _ecmHost: string = AlfrescoSettingsService.DEFAULT_ECM_ADDRESS; private _bpmHost: string = AlfrescoSettingsService.DEFAULT_BPM_ADDRESS; + private _csrfDisabled: boolean = AlfrescoSettingsService.DEFAULT_CSRF_CONFIG; private _bpmContextPath = AlfrescoSettingsService.DEFAULT_BPM_CONTEXT_PATH; @@ -35,12 +37,18 @@ export class AlfrescoSettingsService { public bpmHostSubject: Subject = new Subject(); public ecmHostSubject: Subject = new Subject(); + public csrfSubject: Subject = new Subject(); public providerSubject: Subject = new Subject(); public get ecmHost(): string { return this._ecmHost; } + public set csrfDisabled(csrfDisabled: boolean) { + this.csrfSubject.next(csrfDisabled); + this._csrfDisabled = csrfDisabled; + } + public set ecmHost(ecmHostUrl: string) { this.ecmHostSubject.next(ecmHostUrl); this._ecmHost = ecmHostUrl; diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.spec.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.spec.ts index 6b83975c23..36e261ba7d 100644 --- a/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoSettings.spec.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { describe, it, beforeEach } from '@angular/core/testing'; import { AlfrescoSettingsService } from './AlfrescoSettings.service'; describe('AlfrescoSettingsService', () => { @@ -47,6 +46,4 @@ describe('AlfrescoSettingsService', () => { service.bpmHost = address; expect(service.bpmHost).toBe(address); }); - - }); diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslation.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslation.service.spec.ts new file mode 100644 index 0000000000..4ae34e2822 --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslation.service.spec.ts @@ -0,0 +1,64 @@ +/*! + * @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 { AlfrescoTranslationService } from '../services/AlfrescoTranslation.service'; +import { Injector } from '@angular/core'; +import { ResponseOptions, Response, XHRBackend, HttpModule } from '@angular/http'; +import { MockBackend, MockConnection } from '@angular/http/testing'; +import { + TranslateModule +} from 'ng2-translate/ng2-translate'; +import {getTestBed, TestBed} from '@angular/core/testing'; + +const mockBackendResponse = (connection: MockConnection, response: string) => { + connection.mockRespond(new Response(new ResponseOptions({body: response}))); +}; + +describe('AlfrescoTranslationService', () => { + let injector: Injector; + let backend: MockBackend; + let alfrescoTranslationService: AlfrescoTranslationService; + let connection: MockConnection; // this will be set when a new connection is emitted from the backend. + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpModule, TranslateModule.forRoot()], + providers: [ + AlfrescoTranslationService, + {provide: XHRBackend, useClass: MockBackend} + ] + }); + injector = getTestBed(); + backend = injector.get(XHRBackend); + alfrescoTranslationService = injector.get(AlfrescoTranslationService); + backend.connections.subscribe((c: MockConnection) => connection = c); + }); + + it('is defined', () => { + expect(AlfrescoTranslationService).toBeDefined(); + expect(alfrescoTranslationService instanceof AlfrescoTranslationService).toBeTruthy(); + }); + + it('should be able to get translations', () => { + alfrescoTranslationService.use('en'); + alfrescoTranslationService.get('TEST').subscribe((res: string) => { + expect(res).toEqual('This is a test'); + }); + + mockBackendResponse(connection, '{"TEST": "This is a test", "TEST2": "This is another test"}'); + }); +}); diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslation.service.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslation.service.ts index a63322914e..71cef189b6 100644 --- a/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslation.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslation.service.ts @@ -15,26 +15,34 @@ * limitations under the License. */ -import { Injectable, Optional } from '@angular/core'; -import { MissingTranslationHandler, TranslateService } from 'ng2-translate/ng2-translate'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; +import { TranslateService } from 'ng2-translate/ng2-translate'; import { AlfrescoTranslationLoader } from './AlfrescoTranslationLoader.service'; @Injectable() -export class AlfrescoTranslationService extends TranslateService { - userLang: string = 'en' ; +export class AlfrescoTranslationService { + userLang: string = 'en'; - constructor(public currentLoader: AlfrescoTranslationLoader, @Optional() missingTranslationHandler: MissingTranslationHandler) { - super(currentLoader, missingTranslationHandler); - this.userLang = navigator.language.split('-')[0]; // use navigator lang if available - this.userLang = /(fr|en)/gi.test(this.userLang) ? this.userLang : 'en'; - this.setDefaultLang(this.userLang); + constructor(public translate: TranslateService) { + this.userLang = translate.getBrowserLang() || 'en'; + translate.setDefaultLang(this.userLang); } addTranslationFolder(name: string = '') { - if (!this.currentLoader.existComponent(name)) { - this.currentLoader.addComponentList(name); - this.getTranslation(this.userLang); + let loader = this.translate.currentLoader; + if (!loader.existComponent(name)) { + loader.addComponentList(name); + this.translate.getTranslation(this.userLang); } - this.use(this.userLang); + this.translate.use(this.userLang); + } + + use(lang: string): Observable { + return this.translate.use(lang); + } + + get(key: string|Array, interpolateParams?: Object): Observable { + return this.translate.get(key, interpolateParams); } } diff --git a/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslationLoader.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslationLoader.service.spec.ts new file mode 100644 index 0000000000..a31feec6b3 --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/services/AlfrescoTranslationLoader.service.spec.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 { TranslateLoader } from 'ng2-translate/ng2-translate'; +import { AlfrescoTranslationLoader } from '../services/AlfrescoTranslationLoader.service'; +import { AlfrescoTranslationService } from '../services/AlfrescoTranslation.service'; +import { Injector } from '@angular/core'; +import { XHRBackend, HttpModule } from '@angular/http'; +import { MockBackend, MockConnection } from '@angular/http/testing'; +import { + TranslateModule +} from 'ng2-translate/ng2-translate'; +import {getTestBed, TestBed} from '@angular/core/testing'; + +describe('TranslateLoader', () => { + let injector: Injector; + let backend: MockBackend; + let alfrescoTranslationService: AlfrescoTranslationService; + let connection: MockConnection; // this will be set when a new connection is emitted from the backend. + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpModule, TranslateModule.forRoot({ + provide: TranslateLoader, + useClass: AlfrescoTranslationLoader + })], + providers: [ + AlfrescoTranslationService, + {provide: XHRBackend, useClass: MockBackend} + ] + }); + injector = getTestBed(); + backend = injector.get(XHRBackend); + alfrescoTranslationService = injector.get(AlfrescoTranslationService); + backend.connections.subscribe((c: MockConnection) => connection = c); + }); + + it('should be able to provide any TranslateLoader', () => { + expect(alfrescoTranslationService).toBeDefined(); + expect(alfrescoTranslationService.translate.currentLoader).toBeDefined(); + expect(alfrescoTranslationService.translate.currentLoader instanceof AlfrescoTranslationLoader).toBeTruthy(); + }); +}); diff --git a/ng2-components/ng2-alfresco-core/src/services/index.ts b/ng2-components/ng2-alfresco-core/src/services/index.ts index 7c013a4b4c..b17ea23c9a 100644 --- a/ng2-components/ng2-alfresco-core/src/services/index.ts +++ b/ng2-components/ng2-alfresco-core/src/services/index.ts @@ -15,9 +15,10 @@ * limitations under the License. */ +export * from './AlfrescoApi.service'; export * from './AlfrescoSettings.service'; export * from './AlfrescoTranslationLoader.service'; export * from './AlfrescoTranslation.service'; -export * from './AlfrescoPipeTranslate.service'; export * from './AlfrescoAuthentication.service'; export * from './AlfrescoContent.service'; +export * from './renditions.service'; diff --git a/ng2-components/ng2-alfresco-core/src/services/renditions.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/renditions.service.spec.ts new file mode 100644 index 0000000000..747c88bdec --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/services/renditions.service.spec.ts @@ -0,0 +1,152 @@ +/*! + * @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 { ReflectiveInjector } from '@angular/core'; +import { AlfrescoApiService } from './AlfrescoApi.service'; +import { RenditionsService } from './renditions.service'; +import { + fakeRedition, + fakeReditionCreated, + fakeReditionsList +} from '../assets/renditionsService.mock'; + +declare let jasmine: any; +declare let AlfrescoApi: any; + +describe('RenditionsService', () => { + let service, injector; + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + AlfrescoApiService, + RenditionsService + ]); + }); + + beforeEach(() => { + jasmine.Ajax.install(); + service = injector.get(RenditionsService); + service.apiService.setInstance(new AlfrescoApi({})); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Get redition list service should return the list', (done) => { + service.getRenditionsListByNodeId('fake-node-id').subscribe((res) => { + expect(res.list.entries[0].entry.id).toBe('avatar'); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeReditionsList) + }); + }); + + it('Create redition service should call the server with the ID passed and the asked encoding', (done) => { + service.createRendition('fake-node-id', 'pdf').subscribe((res) => { + expect(jasmine.Ajax.requests.mostRecent().url).toBe('http://127.0.0.1:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fake-node-id/renditions'); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: '' + }); + }); + + it('Get redition service should catch the error', (done) => { + service.getRenditionsListByNodeId('fake-node-id').subscribe((res) => { + }, (res) => { + done(); + } + ); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 403, + contentType: 'application/json', + responseText: 'error' + }); + }); + + it('isConversionPossible should return true if is possible convert', (done) => { + service.isConversionPossible('fake-node-id', 'pdf').subscribe((res) => { + expect(res).toBe(true); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeRedition) + }); + }); + + it('isConversionPossible should return false if is not possible to convert', (done) => { + service.isConversionPossible('fake-node-id', 'pdf').subscribe((res) => { + expect(res).toBe(false); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 403, + contentType: 'application/json' + }); + }); + + it('isRenditionsAvailable should return true if the conversion exist', (done) => { + service.isRenditionAvailable('fake-node-id', 'pdf').subscribe((res) => { + expect(res).toBe(true); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeReditionCreated) + }); + }); + + it('isRenditionsAvailable should return false if the conversion not exist', (done) => { + service.isRenditionAvailable('fake-node-id', 'pdf').subscribe((res) => { + expect(res).toBe(false); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'application/json', + responseText: JSON.stringify(fakeRedition) + }); + }); + + it('isRenditionsAvailable should return false if the conversion get error', (done) => { + service.isRenditionAvailable('fake-node-id', 'pdf').subscribe((res) => { + expect(res).toBe(false); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 400, + contentType: 'application/json' + }); + }); +}); diff --git a/ng2-components/ng2-alfresco-core/src/services/renditions.service.ts b/ng2-components/ng2-alfresco-core/src/services/renditions.service.ts new file mode 100644 index 0000000000..8b36899227 --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/services/renditions.service.ts @@ -0,0 +1,81 @@ +/*! + * @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 { AlfrescoApiService } from './AlfrescoApi.service'; + +/** + * RenditionsService + * + * @returns {RenditionsService} . + */ +@Injectable() +export class RenditionsService { + + constructor(private apiService: AlfrescoApiService) { + + } + + isRenditionAvailable(nodeId: string, encoding: string) { + return Observable.create((observer) => { + this.getRendition(nodeId, encoding).subscribe((res) => { + let isAvailable = true; + if (res.entry.status === 'NOT_CREATED') { + isAvailable = false; + } + observer.next(isAvailable); + observer.complete(); + }, () => { + observer.next(false); + observer.complete(); + }); + }); + } + + isConversionPossible(nodeId: string, encoding: string) { + return Observable.create((observer) => { + this.getRendition(nodeId, encoding).subscribe(() => { + observer.next(true); + observer.complete(); + }, () => { + observer.next(false); + observer.complete(); + }); + }); + } + + getRendition(nodeId: string, encoding: string) { + return Observable.fromPromise(this.apiService.getInstance().core.renditionsApi.getRendition(nodeId, encoding)) + .catch(this.handleError); + } + + getRenditionsListByNodeId(nodeId: string) { + return Observable.fromPromise(this.apiService.getInstance().core.renditionsApi.getRenditions(nodeId)) + .catch(this.handleError); + } + + createRendition(nodeId: string, encoding: string) { + return Observable.fromPromise(this.apiService.getInstance().core.renditionsApi.createRendition(nodeId, {id: encoding})) + .catch(this.handleError); + } + + private handleError(error: any): Observable { + console.error(error); + return Observable.throw(error || 'Server error'); + } +} diff --git a/ng2-components/ng2-alfresco-core/src/utils/object-utils.spec.ts b/ng2-components/ng2-alfresco-core/src/utils/object-utils.spec.ts index e63fb90cd8..0ff8626a85 100644 --- a/ng2-components/ng2-alfresco-core/src/utils/object-utils.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/utils/object-utils.spec.ts @@ -15,11 +15,6 @@ * limitations under the License. */ -import { - it, - describe, - expect -} from '@angular/core/testing'; import { ObjectUtils } from './object-utils'; describe('ObjectUtils', () => { diff --git a/ng2-components/ng2-alfresco-core/tsconfig.json b/ng2-components/ng2-alfresco-core/tsconfig.json index 39deca0930..7be35bfec8 100644 --- a/ng2-components/ng2-alfresco-core/tsconfig.json +++ b/ng2-components/ng2-alfresco-core/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es5", - "module": "system", + "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, @@ -15,13 +15,12 @@ "noImplicitReturns": false, "noImplicitUseStrict": false, "noFallthroughCasesInSwitch": true, - "outDir": "dist" + "outDir": "dist", + "types": ["core-js", "jasmine", "node"] }, "exclude": [ "demo", "node_modules", - "typings/main", - "typings/main.d.ts", "dist" ] } diff --git a/ng2-components/ng2-alfresco-core/tslint.json b/ng2-components/ng2-alfresco-core/tslint.json index 23d636b1eb..4ce54ea73b 100644 --- a/ng2-components/ng2-alfresco-core/tslint.json +++ b/ng2-components/ng2-alfresco-core/tslint.json @@ -35,7 +35,7 @@ "no-arg": true, "no-bitwise": false, "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, + "no-consecutive-blank-lines": true, "no-console": [ true, "debug", diff --git a/ng2-components/ng2-alfresco-core/typings.json b/ng2-components/ng2-alfresco-core/typings.json deleted file mode 100644 index d8954c2485..0000000000 --- a/ng2-components/ng2-alfresco-core/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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-datatable/.npmignore b/ng2-components/ng2-alfresco-datatable/.npmignore index 16ba3e61e5..c5ca623298 100644 --- a/ng2-components/ng2-alfresco-datatable/.npmignore +++ b/ng2-components/ng2-alfresco-datatable/.npmignore @@ -1,7 +1,6 @@ npm-debug.log .idea -assets/ coverage/ node_modules typings/ diff --git a/ng2-components/ng2-alfresco-datatable/README.md b/ng2-components/ng2-alfresco-datatable/README.md index 7f68b788ff..60f33a654d 100644 --- a/ng2-components/ng2-alfresco-datatable/README.md +++ b/ng2-components/ng2-alfresco-datatable/README.md @@ -34,96 +34,133 @@ ## Prerequisites -Before you start using this development framework, make sure you have installed all required software and done all the +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-alfresco-datatable -``` +Follow the 3 steps below: -### Dependencies +1. Npm -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 ng2-alfresco-datatable --save + ``` -```sh -npm install --save ng2-translate ng2-alfresco-core -``` +2. Html -#### Material Design Lite + Include these dependencies in your index.html page: -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: + ```html -```sh -npm install --save material-design-icons material-design-lite -``` + + + + -Also make sure you include these dependencies in your `index.html` file: + + + -```html - - - - -``` + + + + + + + + + + + + + + + + ``` + +3. SystemJs + + Add the following components to your systemjs.config.js file: + + - ng2-translate + - alfresco-js-api + - ng2-alfresco-core + - ng2-alfresco-datatable + + Please refer to the following example file: [systemjs.config.js](demo/systemjs + .config.js) . ## Basic usage example +Usage example of this component : **my.component.ts** ```ts + +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { CoreModule } from 'ng2-alfresco-core'; +import { DataTableModule } from 'ng2-alfresco-datatable'; +import { ObjectDataTableAdapter } from 'ng2-alfresco-datatable'; import { Component } from '@angular/core'; -import { - CONTEXT_MENU_DIRECTIVES, - CONTEXT_MENU_PROVIDERS -} from 'ng2-alfresco-core'; -import { - ALFRESCO_DATATABLE_DIRECTIVES, - ObjectDataTableAdapter -} from 'ng2-alfresco-datatable'; +import { CONTEXT_MENU_DIRECTIVES, CONTEXT_MENU_PROVIDERS } from 'ng2-alfresco-core'; +import { ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter } from 'ng2-alfresco-datatable'; @Component({ - selector: 'my-view', + selector: 'alfresco-app-demo', template: ` `, directives: [ALFRESCO_DATATABLE_DIRECTIVES, CONTEXT_MENU_DIRECTIVES], providers: [CONTEXT_MENU_PROVIDERS] }) -export class MyView { +export class DataTableDemo { data: ObjectDataTableAdapter; constructor() { this.data = new ObjectDataTableAdapter( // data [ - { id: 1, name: 'Name 1' }, - { id: 2, name: 'Name 2' } + {id: 1, name: 'Name 1'}, + {id: 2, name: 'Name 2'} ], // schema [ - { - type: 'text', - key: 'id', - title: 'Id', - sortable: true + { + type: 'text', + key: 'id', + title: 'Id', + sortable: true }, - { - type: 'text', - key: 'name', - title: 'Name', - cssClass: 'full-width', - sortable: true + { + type: 'text', + key: 'name', + title: 'Name', + cssClass: 'full-width', + sortable: true } ] ); } } + +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + DataTableModule + ], + declarations: [DataTableDemo], + bootstrap: [DataTableDemo] +}) +export class AppModule { +} + +platformBrowserDynamic().bootstrapModule(AppModule); + + ``` ![DataTable demo](docs/assets/datatable-demo.png) @@ -132,9 +169,10 @@ export class MyView { | Name | Type | Default | Description | --- | --- | --- | --- | -| data | DataTableAdapter | instance of **ObjectDataTableAdapter** | data source | -| multiselect | boolean | false | Toggles multiple row selection, renders checkboxes at the beginning of each row | -| actions | boolean | false | Toggles data actions column | +| `data` | DataTableAdapter | instance of **ObjectDataTableAdapter** | data source | +| `multiselect` | boolean | false | Toggles multiple row selection, renders checkboxes at the beginning of each row | +| `actions` | boolean | false | Toggles data actions column | +| `fallbackThubnail` | string | | Fallback image for row ehre thubnail is missing| ### Events @@ -365,6 +403,7 @@ let data = new ObjectDataTableAdapter( ``` ## Generate schema + Is possible to auto generate your schema if you have only the data row ```ts @@ -396,11 +435,11 @@ let schema = ObjectDataTableAdapter.generateSchema(data); ``` - ## Build from sources Alternatively you can build component from sources with the following commands: + ```sh npm install npm run build @@ -411,8 +450,8 @@ npm run build ```sh $ npm run build:w ``` - -### Running unit tests + +## Running unit tests ```sh npm test @@ -424,11 +463,35 @@ npm test npm test-browser ``` -This task rebuilds all the code, runs tslint, license checks and other quality check tools -before performing unit testing. +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 ``` + +## Demo + +If you want have a demo of how the component works, please check the demo folder : + +```sh +cd demo +npm install +npm start +``` + +## NPM scripts + +| Command | Description | +| --- | --- | +| npm run build | Build component | +| npm run build:w | Build component and keep watching the changes | +| npm run test | Run unit tests in the console | +| npm run test-browser | Run unit tests in the browser +| npm run coverage | Run unit tests and display code coverage report | + +## License + +[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE) \ No newline at end of file diff --git a/ng2-components/ng2-alfresco-datatable/demo/index.html b/ng2-components/ng2-alfresco-datatable/demo/index.html index c32a86e1e9..c9a7a00b04 100644 --- a/ng2-components/ng2-alfresco-datatable/demo/index.html +++ b/ng2-components/ng2-alfresco-datatable/demo/index.html @@ -26,7 +26,7 @@ - + diff --git a/ng2-components/ng2-alfresco-datatable/demo/package.json b/ng2-components/ng2-alfresco-datatable/demo/package.json index 34516e71b7..b9ff78255c 100644 --- a/ng2-components/ng2-alfresco-datatable/demo/package.json +++ b/ng2-components/ng2-alfresco-datatable/demo/package.json @@ -11,9 +11,8 @@ ], "main": "index.js", "scripts": { - "clean": "rimraf dist node_modules typings", - "postinstall": "npm run typings && npm run build", - "typings": "typings install", + "clean": "npm install rimraf && rimraf dist node_modules typings dist", + "postinstall": "npm run build", "build": "npm run tslint && rimraf dist && npm run tsc", "start": "npm run build && concurrently \"npm run tsc:w\" \"npm run server\" ", "server": "wsrv -o -l -s", @@ -23,35 +22,39 @@ }, "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", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", "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", + "zone.js": "^0.6.23", + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", "material-design-icons": "2.2.3", - "material-design-lite": "1.1.3", - "ng2-activiti-processlist": "^0.1.0" + "material-design-lite": "1.2.1", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0", + "ng2-alfresco-datatable": "0.4.0" }, "devDependencies": { - "browser-sync": "2.10.0", - "concurrently": "2.0.0", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", "rimraf": "2.5.2", - "tslint": "3.8.1", - "typescript": "1.8.10", - "typings": "1.0.4", - "wsrv": "0.1.3" + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" }, "keywords": [ "angular2", diff --git a/ng2-components/ng2-alfresco-datatable/demo/src/main.ts b/ng2-components/ng2-alfresco-datatable/demo/src/main.ts index be2f2e9327..8b9daed126 100644 --- a/ng2-components/ng2-alfresco-datatable/demo/src/main.ts +++ b/ng2-components/ng2-alfresco-datatable/demo/src/main.ts @@ -15,11 +15,14 @@ * limitations under the License. */ -import { Component } from '@angular/core'; -import { bootstrap } from '@angular/platform-browser-dynamic'; -import { CONTEXT_MENU_DIRECTIVES, CONTEXT_MENU_PROVIDERS } from 'ng2-alfresco-core'; +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { CoreModule } from 'ng2-alfresco-core'; +import { DataTableModule } from 'ng2-alfresco-datatable'; + import { - ALFRESCO_DATATABLE_DIRECTIVES, ObjectDataTableAdapter, DataSorting, ObjectDataRow, @@ -27,7 +30,7 @@ import { } from 'ng2-alfresco-datatable'; @Component({ - selector: 'alfresco-datatable-demo', + selector: 'alfresco-app-demo', template: `
@@ -66,8 +69,7 @@ import { styles: [ ':host > .container {padding: 10px}', '.p-10 { padding: 10px; }' - ], - directives: [ALFRESCO_DATATABLE_DIRECTIVES, CONTEXT_MENU_DIRECTIVES] + ] }) class DataTableDemo { @@ -136,6 +138,15 @@ class DataTableDemo { } } -bootstrap(DataTableDemo, [ - CONTEXT_MENU_PROVIDERS -]); +@NgModule({ + imports: [ + BrowserModule, + CoreModule.forRoot(), + DataTableModule + ], + declarations: [ DataTableDemo ], + bootstrap: [ DataTableDemo ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/ng2-components/ng2-alfresco-datatable/demo/systemjs.config.js b/ng2-components/ng2-alfresco-datatable/demo/systemjs.config.js index 87b7c9ec25..7deffeb4b2 100644 --- a/ng2-components/ng2-alfresco-datatable/demo/systemjs.config.js +++ b/ng2-components/ng2-alfresco-datatable/demo/systemjs.config.js @@ -2,54 +2,45 @@ * 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' - }; - // 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' } - }; - 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); +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'dist', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', + 'ng2-alfresco-datatable': 'npm:ng2-alfresco-datatable/dist' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'ng2-translate': { defaultExtension: 'js' }, + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, + 'ng2-alfresco-datatable': { main: './index.js', defaultExtension: 'js'} + } + }); })(this); diff --git a/ng2-components/ng2-alfresco-datatable/demo/tsconfig.json b/ng2-components/ng2-alfresco-datatable/demo/tsconfig.json index f6761b5218..7be35bfec8 100644 --- a/ng2-components/ng2-alfresco-datatable/demo/tsconfig.json +++ b/ng2-components/ng2-alfresco-datatable/demo/tsconfig.json @@ -1,19 +1,26 @@ { "compilerOptions": { - "target": "ES5", - "module": "system", + "target": "es5", + "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, "removeComments": true, "declaration": true, - "outDir": "dist" + "noLib": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": false, + "noImplicitReturns": false, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "outDir": "dist", + "types": ["core-js", "jasmine", "node"] }, "exclude": [ - "dist", + "demo", "node_modules", - "typings/main", - "typings/main.d.ts" + "dist" ] } diff --git a/ng2-components/ng2-alfresco-datatable/demo/tslint.json b/ng2-components/ng2-alfresco-datatable/demo/tslint.json index 8c48e76469..55c0f8a666 100644 --- a/ng2-components/ng2-alfresco-datatable/demo/tslint.json +++ b/ng2-components/ng2-alfresco-datatable/demo/tslint.json @@ -38,7 +38,7 @@ "no-arg": true, "no-bitwise": true, "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, + "no-consecutive-blank-lines": true, "no-console": [ true, "debug", diff --git a/ng2-components/ng2-alfresco-datatable/demo/typings.json b/ng2-components/ng2-alfresco-datatable/demo/typings.json deleted file mode 100644 index d8954c2485..0000000000 --- a/ng2-components/ng2-alfresco-datatable/demo/typings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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-datatable/index.ts b/ng2-components/ng2-alfresco-datatable/index.ts index bb3a843a1b..3e88aeddae 100644 --- a/ng2-components/ng2-alfresco-datatable/index.ts +++ b/ng2-components/ng2-alfresco-datatable/index.ts @@ -15,5 +15,34 @@ * limitations under the License. */ +import { NgModule } from '@angular/core'; +import { CoreModule } from 'ng2-alfresco-core'; + export * from './src/data/index'; export * from './src/components/index'; +export * from './src/components/pagination/index'; + +import { DataTableComponent } from './src/components/datatable/datatable.component'; +import { NoContentTemplateComponent } from './src/components/datatable/no-content-template.component'; +import { PaginationComponent } from './src/components/pagination/pagination.component'; + +export const ALFRESCO_DATATABLE_DIRECTIVES: [any] = [ + DataTableComponent, + NoContentTemplateComponent, + PaginationComponent +]; + +@NgModule({ + imports: [ + CoreModule + ], + declarations: [ + ...ALFRESCO_DATATABLE_DIRECTIVES + ], + providers: [ + ], + exports: [ + ...ALFRESCO_DATATABLE_DIRECTIVES + ] +}) +export class DataTableModule {} diff --git a/ng2-components/ng2-alfresco-datatable/karma-test-shim.js b/ng2-components/ng2-alfresco-datatable/karma-test-shim.js index 2d378ec947..a58f01a39b 100644 --- a/ng2-components/ng2-alfresco-datatable/karma-test-shim.js +++ b/ng2-components/ng2-alfresco-datatable/karma-test-shim.js @@ -5,103 +5,100 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; __karma__.loaded = function() {}; +var builtPath = '/base/dist/'; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +var paths = { + // paths serve as alias + 'npm:': 'base/node_modules/' +}; + 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' + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // testing + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'ng2-translate': 'npm:ng2-translate', + + 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist' }; var packages = { 'app': { main: 'main.js', defaultExtension: 'js' }, - 'rxjs': { defaultExtension: 'js' }, - 'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' }, - 'ng2-translate': { defaultExtension: 'js' } -}; + 'rxjs': { 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, {}) + 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'} }; var config = { + paths: paths, 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') - ]) - }) +System.import('@angular/core/testing') + .then(initTestBed) + .then(initTesting); + +function initTestBed(){ + 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); + var coreTesting = providers[0]; + var browserTesting = providers[1]; + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); }) - .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') +// Import all spec files and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { return System.import(moduleName); - }); + }) + ) + .then(__karma__.start, __karma__.error); } diff --git a/ng2-components/ng2-alfresco-datatable/karma.conf.js b/ng2-components/ng2-alfresco-datatable/karma.conf.js index d875822935..94b9ff4994 100644 --- a/ng2-components/ng2-alfresco-datatable/karma.conf.js +++ b/ng2-components/ng2-alfresco-datatable/karma.conf.js @@ -4,27 +4,48 @@ module.exports = function (config) { var configuration = { basePath: '.', - frameworks: [/*'jasmine-ajax',*/ '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/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}, + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', - {pattern: 'karma-test-shim.js', included: true, watched: true}, + // Polyfills + 'node_modules/core-js/client/shim.js', + 'node_modules/reflect-metadata/Reflect.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs + { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, + { pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, + + // Paths loaded via module imports: + // Angular itself + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, + + 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/ng2-translate/**/*.js.map', included: false, watched: false}, + + 'karma-test-shim.js', // 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}, + // ng2-components + { pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false }, + // 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} @@ -63,7 +84,7 @@ module.exports = function (config) { plugins: [ 'karma-jasmine', 'karma-coverage', - //'karma-jasmine-ajax', + 'karma-jasmine-ajax', 'karma-chrome-launcher', 'karma-mocha-reporter', 'karma-jasmine-html-reporter' @@ -75,10 +96,11 @@ module.exports = function (config) { // 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'] + 'dist/**/!(*spec|index|*mock|*model|*interface).js': 'coverage' }, coverageReporter: { + includeAllSources: true, dir: 'coverage/', subdir: 'report', reporters: [ diff --git a/ng2-components/ng2-alfresco-datatable/package.json b/ng2-components/ng2-alfresco-datatable/package.json index a703b9b1b2..1269fed714 100644 --- a/ng2-components/ng2-alfresco-datatable/package.json +++ b/ng2-components/ng2-alfresco-datatable/package.json @@ -1,13 +1,10 @@ { "name": "ng2-alfresco-datatable", "description": "Alfresco Angular2 DataTable Component", - "version": "0.3.0", + "version": "0.4.0", "author": "Alfresco Software, Ltd.", "scripts": { - "postinstall": "typings install", - "clean": "rimraf dist node_modules typings", - "typings": "typings install", - "server": "wsrv -o -p 9875", + "clean": "npm install rimraf && rimraf dist node_modules typings", "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\"", @@ -47,48 +44,50 @@ "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", + "@angular/common": "2.0.0", + "@angular/compiler": "2.0.0", + "@angular/core": "2.0.0", + "@angular/forms": "2.0.0", + "@angular/http": "2.0.0", + "@angular/platform-browser": "2.0.0", + "@angular/platform-browser-dynamic": "2.0.0", + "core-js": "^2.4.1", + "reflect-metadata": "^0.1.3", + "rxjs": "5.0.0-beta.12", "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" + "zone.js": "^0.6.23", + + "intl": "1.2.4", + "dialog-polyfill": "^0.4.3", + "element.scrollintoviewifneeded-polyfill": "^1.0.1", + "material-design-icons": "2.2.3", + "material-design-lite": "1.2.1", + + "ng2-translate": "2.5.0", + "alfresco-js-api": "^0.4.0", + "ng2-alfresco-core": "0.4.0" }, "devDependencies": { - "concurrently": "2.1.0", - "coveralls": "2.11.9", + "@types/node": "^6.0.42", + "@types/core-js": "^0.9.32", + "@types/jasmine": "^2.2.33", + "concurrently": "^2.2.0", "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-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" + "tslint": "3.15.1", + "typescript": "^2.0.3", + "wsrv": "^0.1.5" }, "license-check-config": { "src": [ @@ -98,5 +97,6 @@ "blocking": true, "logInfo": false, "logError": true - } + }, + "license": "Apache-2.0" } diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.css b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.css index 7f76443680..edc97ce169 100644 --- a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.css +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.css @@ -87,6 +87,10 @@ border: 0; } +.hidden { + display: none; +} + /* small desktop */ @media all and (max-width: 1200px) {} @@ -105,3 +109,8 @@ display: none; } } + +.mdl-data-table-fix-firefox{ + border-collapse: unset; + border-spacing: 0; +} diff --git a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.html b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.html index 537efb6226..b7f9035c1d 100644 --- a/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.html +++ b/ng2-components/ng2-alfresco-datatable/src/components/datatable/datatable.component.html @@ -1,6 +1,6 @@ + class="mdl-data-table mdl-js-data-table full-width mdl-data-table-fix-firefox"> @@ -31,7 +31,7 @@ - +