@@ -1,39 +1,38 @@
|
||||
**Please check if the PR fulfills these requirements**
|
||||
- [ ] The commit message follows our [guidelines](https://github.com/Alfresco/alfresco-ng2-components/wiki/Commit-format)
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
<!--
|
||||
Before submitting your PR, please check that your code follows our contribution guidelines:
|
||||
https://github.com/Alfresco/alfresco-ng2-components/wiki/Code-contribution-acceptance-criteria
|
||||
|
||||
Check also that your commit messages follow our commit message format guidelines:
|
||||
https://github.com/Alfresco/alfresco-ng2-components/wiki/Commit-format
|
||||
-->
|
||||
|
||||
**Type of contribution:** (check one with "x")
|
||||
**What kind of change does this PR introduce?** (check one with "x")
|
||||
```
|
||||
[ ] Bugfix
|
||||
[ ] Feature
|
||||
[ ] Code formatting
|
||||
[ ] Code Refactoring
|
||||
[ ] Code style update (formatting, local variables)
|
||||
[ ] Refactoring (no functional changes, no api changes)
|
||||
[ ] Build related changes
|
||||
[ ] Documentation
|
||||
[ ] Other... Please describe:
|
||||
```
|
||||
**This PR adds the following feature:**
|
||||
<!-- you can ignore this line in the case of a bugfix -->
|
||||
|
||||
**Current behavior:**
|
||||
**What is the current behavior?** (You can also link to an open issue here)
|
||||
|
||||
**New behavior:**
|
||||
|
||||
**This PR fixes the following issue:**
|
||||
<!-- link to the open issue, ignore this if there are no issues open -->
|
||||
|
||||
**This PR introduces a breaking change:** (check one with "x")
|
||||
**What is the new behavior?**
|
||||
|
||||
|
||||
|
||||
**Does this PR introduce a breaking change?** (check one with "x")
|
||||
```
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
[ ] Yes
|
||||
[ ] No
|
||||
```
|
||||
|
||||
<!-- Please describe the reason to introduce a breaking change, and what exactly breaks -->
|
||||
|
||||
|
||||
**More information:**
|
||||
If this PR contains a breaking change, please describe the impact and migration path for existing applications: ...
|
||||
|
||||
**Other information**:
|
||||
@@ -1,22 +1,17 @@
|
||||
language: node_js
|
||||
dist: trusty
|
||||
sudo: required
|
||||
sudo: false
|
||||
|
||||
node_js:
|
||||
- "5"
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
|
||||
|
||||
before_install:
|
||||
- export CHROME_BIN=/usr/bin/google-chrome
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- MODULE=ng2-alfresco-core
|
||||
@@ -37,31 +32,34 @@ env:
|
||||
|
||||
before_script:
|
||||
- if ([ "$MODULE" != "ng2-alfresco-core" ]); then
|
||||
(cd ng2-components/ng2-alfresco-core; npm install; npm link);
|
||||
(cd ng2-components/ng2-alfresco-core; if [ ! -d "dist" ]; then npm install; fi; npm link);
|
||||
fi
|
||||
- if ([ "$MODULE" == "ng2-alfresco-documentlist" ] || [ "$MODULE" == "ng2-alfresco-webscript" ] || [ "$MODULE" == "ng2-activiti-processlist" ] || [ "$MODULE" == "ng2-activiti-tasklist" ]); then
|
||||
(cd ng2-components/ng2-alfresco-datatable; npm link ng2-alfresco-core; npm install; npm link);
|
||||
(cd ng2-components/ng2-alfresco-datatable; npm link ng2-alfresco-core; if [ ! -d "dist" ]; then npm install; fi; npm link);
|
||||
fi
|
||||
- if ([ "$MODULE" == "ng2-activiti-tasklist" ] || [ "$MODULE" == "ng2-activiti-processlist" ]); then
|
||||
(cd ng2-components/ng2-activiti-form; npm link ng2-alfresco-core; npm install; npm link);
|
||||
(cd ng2-components/ng2-activiti-form; npm link ng2-alfresco-core; if [ ! -d "dist" ]; then npm install; fi; npm link);
|
||||
fi
|
||||
- if ([ "$MODULE" == "ng2-activiti-processlist" ]); then
|
||||
(cd ng2-components/ng2-activiti-tasklist; npm link ng2-alfresco-core; npm link ng2-alfresco-datatable; npm link ng2-activiti-form; npm install; npm link);
|
||||
(cd ng2-components/ng2-activiti-tasklist; npm link ng2-alfresco-core; npm link ng2-alfresco-datatable; npm link ng2-activiti-form; if [ ! -d "dist" ]; then npm install; fi; npm link);
|
||||
fi
|
||||
- if ([ "$MODULE" == "ng2-activiti-analytics" ]); then
|
||||
(cd ng2-components/ng2-activiti-diagrams; npm link ng2-alfresco-core; npm install; npm link);
|
||||
(cd ng2-components/ng2-activiti-diagrams; npm link ng2-alfresco-core; if [ ! -d "dist" ]; then npm install; fi; npm link);
|
||||
fi
|
||||
- cd ng2-components/$MODULE;
|
||||
- npm run travis
|
||||
- npm run travis;
|
||||
- npm install;
|
||||
- ls -ltrh ./node_modules/
|
||||
script: npm run test
|
||||
# Send coverage data to Coveralls
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
- if ([ "$TRAVIS_BRANCH" == "development" ] || [ "$TRAVIS_BRANCH" == "master" ]); then
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
fi
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- ./node_modules/material-design-lite
|
||||
- ./node_modules/material-design-icons
|
||||
- demo-shell-ng2/node_modules
|
||||
- ng2-components/ng2-activiti-form/node_modules
|
||||
- ng2-components/ng2-activiti-processlist/node_modules
|
||||
|
||||
@@ -6,6 +6,7 @@ The [Angular 2](https://angular.io/) based application development framework req
|
||||
- [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.
|
||||
- local nginx proxy to avoid cross-origin browser restrictions (see below)
|
||||
- (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.*
|
||||
@@ -25,26 +26,39 @@ $ node -v
|
||||
v5.12.0
|
||||
```
|
||||
|
||||
## Configure Nginx
|
||||
## Installing 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.
|
||||
Most Linux distributions will come with nginx available to install via your
|
||||
package manager and on Mac OS you can use [Homebrew](http://brew.sh/).
|
||||
|
||||
If you want to install manually however you can follow the instructions on the
|
||||
[download page](http://nginx.org/en/download.html). See also the specific information for
|
||||
[windows users](http://nginx.org/en/docs/windows.html).
|
||||
|
||||
### Start nginx
|
||||
|
||||
Start nginx using the supplied configuration in [nginx.conf](nginx.conf)
|
||||
|
||||
nginx -c nginx.conf
|
||||
|
||||
### Review nginx configuration
|
||||
|
||||
To correctly configure nginx use the following file [nginx.conf](/nginx.conf).
|
||||
This will host Activiti, Alfresco and the app dev framework under the same origin.
|
||||
|
||||
* 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.
|
||||
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 right settings 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
|
||||
* nginx entry point: 0.0.0.0:8888
|
||||
* Demo Shell: locathost:3000
|
||||
* Alfresco: locathost:8080
|
||||
* Activiti: locathost: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)
|
||||
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)
|
||||
|
||||
@@ -36,6 +36,7 @@ install:
|
||||
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
|
||||
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-activiti-form && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
|
||||
- IF %COMPONENT_NAME% EQU ng2-alfresco-webscript (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
|
||||
- IF %COMPONENT_NAME% EQU ng2-activiti-analytics (cd ng2-components/ng2-activiti-diagrams && npm link link ng2-alfresco-core && npm install && npm link && cd ../../)
|
||||
- cd ng2-components/%COMPONENT_NAME%
|
||||
- IF %COMPONENT_NAME% NEQ ng2-alfresco-core (npm link ng2-alfresco-core)
|
||||
- IF %COMPONENT_NAME% EQU ng2-alfresco-documentlist (npm link ng2-alfresco-datatable)
|
||||
@@ -55,3 +56,21 @@ test_script:
|
||||
|
||||
# Don't actually build.
|
||||
build: off
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-activiti-form\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-activiti-processlist\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-activiti-tasklist\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-core\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-datatable\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-documentlist\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-login\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-search\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-upload\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-viewer\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-webscript\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-tag\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-activiti-analytics\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-alfresco-userinfo\node_modules
|
||||
- C:\projects\alfresco-ng2-components\ng2-components\ng2-activiti-diagrams\node_modules
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
margin-right: 50px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mdl-button-padding {
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
<header class="mdl-layout__header">
|
||||
<div class="mdl-button-padding mdl-layout__header-row">
|
||||
|
||||
<div *ngIf="isLoggedIn()">
|
||||
<ng2-alfresco-userinfo class="user-profile" [menuOpenType]="left">
|
||||
</ng2-alfresco-userinfo>
|
||||
</div>
|
||||
|
||||
<ng2-alfresco-userinfo class="user-profile" [menuOpenType]="left">
|
||||
</ng2-alfresco-userinfo>
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
@@ -20,7 +19,8 @@
|
||||
|
||||
<!-- Navigation. We hide it in small screens. -->
|
||||
<nav class="mdl-navigation mdl-layout--large-screen-only">
|
||||
<a class="mdl-navigation__link" data-automation-id="files" href="" routerLink="/files">DocumentList</a>
|
||||
<a class="mdl-navigation__link" data-automation-id="files" href=""
|
||||
routerLink="/files">DocumentList</a>
|
||||
<a class="mdl-navigation__link" data-automation-id="datatable" href="" routerLink="/datatable">DataTable</a>
|
||||
<a class="mdl-navigation__link" data-automation-id="uploader" href="" routerLink="/uploader">Uploader</a>
|
||||
<a class="mdl-navigation__link" data-automation-id="activiti" href="" routerLink="/activiti">Activiti</a>
|
||||
@@ -66,10 +66,12 @@
|
||||
<span class="mdl-layout-title">Components List</span>
|
||||
<nav class="mdl-navigation">
|
||||
<a class="mdl-navigation__link" href="" routerLink="/files" (click)="hideDrawer()">DocumentList Demo</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/datatable" (click)="hideDrawer()">DataTable Demo</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/datatable" (click)="hideDrawer()">DataTable
|
||||
Demo</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/uploader" (click)="hideDrawer()">Uploader Demo</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/login" (click)="hideDrawer()">Login Demo</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/activiti" (click)="hideDrawer()">Activiti Components Demo</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/activiti" (click)="hideDrawer()">Activiti
|
||||
Components Demo</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/webscript" (click)="hideDrawer()">Webscript</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/tag" (click)="hideDrawer()">Tag</a>
|
||||
<a class="mdl-navigation__link" href="" routerLink="/settings" (click)="hideDrawer()">Settings</a>
|
||||
|
||||
@@ -46,7 +46,8 @@ export class AppComponent {
|
||||
this.setProvider();
|
||||
|
||||
if (translate) {
|
||||
translate.addTranslationFolder();
|
||||
translate.addTranslationFolder('custom', 'custom-translation/');
|
||||
translate.addTranslationFolder('ng2-alfresco-login', 'custom-translation/alfresco-login');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import { AnalyticsModule } from 'ng2-activiti-analytics';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { routing } from './app.routes';
|
||||
import { CustomEditorsModule } from './components/activiti/custom-editor/custom-editor.component';
|
||||
|
||||
import {
|
||||
DataTableDemoComponent,
|
||||
@@ -68,7 +69,8 @@ import {
|
||||
ActivitiTaskListModule.forRoot(),
|
||||
ActivitiProcessListModule.forRoot(),
|
||||
UserInfoComponentModule.forRoot(),
|
||||
AnalyticsModule.forRoot()
|
||||
AnalyticsModule.forRoot(),
|
||||
CustomEditorsModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
|
||||
@@ -12,3 +12,7 @@
|
||||
padding: 10px 10px 10px 10px;
|
||||
border: solid 2px rgb(31,188,210);
|
||||
}
|
||||
|
||||
.mdl-layout__header {
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -36,7 +36,13 @@
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--3-col task-column mdl-shadow--2dp">
|
||||
<span>Task List</span>
|
||||
<activiti-tasklist [taskFilter]="taskFilter" [data]="dataTasks"
|
||||
<activiti-tasklist *ngIf="taskFilter?.hasFilter()" [appId]="taskFilter.appId"
|
||||
[processDefinitionKey]="taskFilter.filter.processDefinitionKey"
|
||||
[name]="taskFilter.filter.name"
|
||||
[assignment]="taskFilter.filter.assignment"
|
||||
[state]="taskFilter.filter.state"
|
||||
[sort]="taskFilter.filter.sort"
|
||||
[data]="dataTasks"
|
||||
(rowClick)="onTaskRowClick($event)" (onSuccess)="onSuccessTaskList($event)"
|
||||
#activititasklist></activiti-tasklist>
|
||||
</div>
|
||||
@@ -58,21 +64,30 @@
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--2-col task-column">
|
||||
<span>Process Filters</span>
|
||||
<activiti-start-process-instance [appId]="appId" (start)="onStartProcessInstance($event)"></activiti-start-process-instance>
|
||||
<button type="button" (click)="navigateStartProcess()" class="mdl-button" data-automation-id="btn-start-process">Start Process</button>
|
||||
<activiti-process-instance-filters [appId]="appId"
|
||||
(filterClick)="onProcessFilterClick($event)" (onSuccess)="onSuccessProcessFilterList($event)"
|
||||
#activitiprocessfilter></activiti-process-instance-filters>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--3-col task-column">
|
||||
<span>Process List</span>
|
||||
<activiti-process-instance-list [filter]="processFilter" [data]="dataProcesses"
|
||||
<activiti-process-instance-list *ngIf="processFilter?.hasFilter()" [appId]="processFilter.appId"
|
||||
[processDefinitionKey]="processFilter.filter.processDefinitionKey"
|
||||
[name]="processFilter.filter.name"
|
||||
[state]="processFilter.filter.state"
|
||||
[sort]="processFilter.filter.sort"
|
||||
[data]="dataProcesses"
|
||||
(rowClick)="onProcessRowClick($event)" (onSuccess)="onSuccessProcessList($event)"
|
||||
#activitiprocesslist></activiti-process-instance-list>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--7-col task-column">
|
||||
<div class="mdl-cell mdl-cell--7-col task-column" *ngIf="!isStartProcessMode()">
|
||||
<span>Process Details</span>
|
||||
<activiti-process-instance-details [processInstanceId]="currentProcessInstanceId" (activitiprocesslist)="taskFormCompleted()" (processCancelled)="processCancelled()" #activitiprocessdetails></activiti-process-instance-details>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--7-col task-column" *ngIf="isStartProcessMode()">
|
||||
<span>Start Process</span>
|
||||
<activiti-start-process [appId]="appId" (start)="onStartProcessInstance($event)"></activiti-start-process>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,11 @@ import {
|
||||
ActivitiApps,
|
||||
ActivitiTaskList
|
||||
} from 'ng2-activiti-tasklist';
|
||||
import { ActivitiProcessInstanceListComponent } from 'ng2-activiti-processlist';
|
||||
import {
|
||||
ActivitiProcessInstanceListComponent,
|
||||
ActivitiStartProcessInstance,
|
||||
ProcessInstance
|
||||
} from 'ng2-activiti-processlist';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Subscription } from 'rxjs/Rx';
|
||||
import {
|
||||
@@ -30,9 +34,14 @@ import {
|
||||
DataSorting
|
||||
} from 'ng2-alfresco-datatable';
|
||||
|
||||
import { FormRenderingService } from 'ng2-activiti-form';
|
||||
import { /*CustomEditorComponent*/ CustomStencil01 } from './custom-editor/custom-editor.component';
|
||||
|
||||
declare let __moduleName: string;
|
||||
declare var componentHandler;
|
||||
|
||||
const currentProcessIdNew = '__NEW__';
|
||||
|
||||
@Component({
|
||||
moduleId: __moduleName,
|
||||
selector: 'activiti-demo',
|
||||
@@ -62,6 +71,9 @@ export class ActivitiDemoComponent implements AfterViewChecked {
|
||||
@ViewChild('activitiprocessdetails')
|
||||
activitiprocessdetails: any;
|
||||
|
||||
@ViewChild(ActivitiStartProcessInstance)
|
||||
activitiStartProcess: ActivitiStartProcessInstance;
|
||||
|
||||
@ViewChild('tabmain')
|
||||
tabMain: any;
|
||||
|
||||
@@ -78,16 +90,16 @@ export class ActivitiDemoComponent implements AfterViewChecked {
|
||||
taskSchemaColumns: any [] = [];
|
||||
processSchemaColumns: any [] = [];
|
||||
|
||||
taskFilter: any;
|
||||
taskFilter: FilterRepresentationModel;
|
||||
report: any;
|
||||
processFilter: any;
|
||||
processFilter: FilterRepresentationModel;
|
||||
|
||||
sub: Subscription;
|
||||
|
||||
dataTasks: ObjectDataTableAdapter;
|
||||
dataProcesses: ObjectDataTableAdapter;
|
||||
|
||||
constructor(private route: ActivatedRoute) {
|
||||
constructor(private route: ActivatedRoute, private formRenderingService: FormRenderingService) {
|
||||
this.dataTasks = new ObjectDataTableAdapter(
|
||||
[],
|
||||
[
|
||||
@@ -100,11 +112,16 @@ export class ActivitiDemoComponent implements AfterViewChecked {
|
||||
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}
|
||||
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column'},
|
||||
{type: 'text', key: 'started', title: 'Started', cssClass: 'hidden'}
|
||||
]
|
||||
);
|
||||
this.dataProcesses.setSorting(new DataSorting('started', 'desc'));
|
||||
|
||||
// Uncomment this line to replace all 'text' field editors with custom component
|
||||
// formRenderingService.setComponentTypeResolver('text', () => CustomEditorComponent, true);
|
||||
|
||||
// Uncomment this line to map 'custom_stencil_01' to local editor component
|
||||
formRenderingService.setComponentTypeResolver('custom_stencil_01', () => CustomStencil01, true);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -154,10 +171,10 @@ export class ActivitiDemoComponent implements AfterViewChecked {
|
||||
}
|
||||
|
||||
onSuccessTaskList(event: FilterRepresentationModel) {
|
||||
this.currentTaskId = this.activititasklist.getCurrentTaskId();
|
||||
this.currentTaskId = this.activititasklist.getCurrentId();
|
||||
}
|
||||
|
||||
onProcessFilterClick(event: any) {
|
||||
onProcessFilterClick(event: FilterRepresentationModel) {
|
||||
this.processFilter = event;
|
||||
}
|
||||
|
||||
@@ -166,7 +183,7 @@ export class ActivitiDemoComponent implements AfterViewChecked {
|
||||
}
|
||||
|
||||
onSuccessProcessList(event: any) {
|
||||
this.currentProcessInstanceId = this.activitiprocesslist.getCurrentProcessId();
|
||||
this.currentProcessInstanceId = this.activitiprocesslist.getCurrentId();
|
||||
}
|
||||
|
||||
onTaskRowClick(taskId) {
|
||||
@@ -177,8 +194,17 @@ export class ActivitiDemoComponent implements AfterViewChecked {
|
||||
this.currentProcessInstanceId = processInstanceId;
|
||||
}
|
||||
|
||||
onStartProcessInstance() {
|
||||
this.activitiprocesslist.reload();
|
||||
navigateStartProcess() {
|
||||
this.currentProcessInstanceId = currentProcessIdNew;
|
||||
}
|
||||
|
||||
onStartProcessInstance(instance: ProcessInstance) {
|
||||
this.currentProcessInstanceId = instance.id;
|
||||
this.activitiStartProcess.reset();
|
||||
}
|
||||
|
||||
isStartProcessMode() {
|
||||
return this.currentProcessInstanceId === currentProcessIdNew;
|
||||
}
|
||||
|
||||
processCancelled(data: any) {
|
||||
@@ -195,7 +221,7 @@ export class ActivitiDemoComponent implements AfterViewChecked {
|
||||
}
|
||||
|
||||
onFormCompleted(form) {
|
||||
this.activititasklist.load(this.taskFilter);
|
||||
this.activititasklist.reload();
|
||||
this.currentTaskId = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NgModule, Component } from '@angular/core';
|
||||
import { WidgetComponent } from 'ng2-activiti-form';
|
||||
|
||||
@Component({
|
||||
selector: 'custom-editor',
|
||||
template: `
|
||||
<div style="color: red">Look, I'm a custom editor!</div>
|
||||
`
|
||||
})
|
||||
export class CustomEditorComponent extends WidgetComponent {}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-stencil-01',
|
||||
template: `<div style="color: green">ADF version of custom Activiti stencil</div>`
|
||||
})
|
||||
export class CustomStencil01 extends WidgetComponent {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [ CustomEditorComponent, CustomStencil01 ],
|
||||
exports: [ CustomEditorComponent, CustomStencil01 ],
|
||||
entryComponents: [ CustomEditorComponent, CustomStencil01 ]
|
||||
})
|
||||
export class CustomEditorsModule {}
|
||||
@@ -73,7 +73,6 @@ export class LoginDemoComponent implements OnInit {
|
||||
}
|
||||
|
||||
onLogin($event) {
|
||||
console.log($event);
|
||||
this.router.navigate(['/home']);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"LOGIN": {
|
||||
"LOGO": "Alfresco",
|
||||
"LABEL": {
|
||||
"LOGIN": "Login",
|
||||
"USERNAME": "Username",
|
||||
"PASSWORD": "Password",
|
||||
"REMEMBER": "Remember"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"USERNAME-REQUIRED": "Required",
|
||||
"USERNAME-MIN": "Your username needs to be at least 4 characters.",
|
||||
"PASSWORD-REQUIRED": "Enter your password to sign in",
|
||||
"LOGIN-ERROR-CREDENTIALS": "You have entered an invalid username or password",
|
||||
"LOGIN-ERROR-PROVIDERS": "Providers cannot be undefined",
|
||||
"LOGIN-SUCCESS": "Login successful"
|
||||
},
|
||||
"BUTTON": {
|
||||
"LOGIN": "SIGN IN"
|
||||
},
|
||||
"ACTION": {
|
||||
"HELP": "NEED HELP?",
|
||||
"REGISTER": "REGISTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"LOGIN": {
|
||||
"LABEL": {
|
||||
"LOGIN": "Autenticazione",
|
||||
"USERNAME": "Nome utente",
|
||||
"PASSWORD": "Password",
|
||||
"REMEMBER": "Ricordami"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"USERNAME-REQUIRED": "Campo obbligatorio",
|
||||
"USERNAME-MIN": "Inserire un nome utente di minimo 4 caratteri.",
|
||||
"PASSWORD-REQUIRED": "Campo obbligatorio",
|
||||
"LOGIN-ERROR-CREDENTIALS": "Nome utente o password non validi",
|
||||
"LOGIN-ERROR-PROVIDERS": "Providers non può essere nullo",
|
||||
"LOGIN-SUCCESS": "Login effettuata con successo"
|
||||
},
|
||||
"BUTTON": {
|
||||
"LOGIN": "Accedi"
|
||||
},
|
||||
"ACTION": {
|
||||
"HELP": "BISOGNO DI AIUTO?",
|
||||
"REGISTER": "REGISTRATI"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"title": "Welcome",
|
||||
|
||||
"DOCUMENT_LIST": {
|
||||
"COLUMNS": {
|
||||
"DISPLAY_NAME": "Display name",
|
||||
"CREATED_BY": "Created by",
|
||||
"CREATED_ON": "Created on"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"FOLDER": {
|
||||
"SYSTEM_1": "System folder action 1",
|
||||
"CUSTOM": "Custom folder action",
|
||||
"DELETE": "Delete"
|
||||
},
|
||||
"DOCUMENT": {
|
||||
"DOWNLOAD": "Download",
|
||||
"DELETE": "Delete",
|
||||
"SYSTEM_2": "System document action 2",
|
||||
"CUSTOM": "Custom action"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,24 +18,8 @@
|
||||
"CUSTOM": "Custom action"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"DATATABLE": {
|
||||
"COLUMNS": {
|
||||
"DISPLAY_NAME": "Display name",
|
||||
"CREATED_BY": "Created by",
|
||||
"CREATED_ON": "Created on"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"FOLDER": {
|
||||
"SYSTEM_1": "System folder action 1",
|
||||
"CUSTOM": "Custom folder action"
|
||||
},
|
||||
"DOCUMENT": {
|
||||
"DOWNLOAD": "Download",
|
||||
"SYSTEM_2": "System document action 2",
|
||||
"CUSTOM": "Custom action"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"title": "Welcome",
|
||||
|
||||
"DOCUMENT_LIST": {
|
||||
"COLUMNS": {
|
||||
"DISPLAY_NAME": "Display name",
|
||||
"CREATED_BY": "Created by",
|
||||
"CREATED_ON": "Created on"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"FOLDER": {
|
||||
"SYSTEM_1": "System folder action 1",
|
||||
"CUSTOM": "Custom folder action",
|
||||
"DELETE": "Delete"
|
||||
},
|
||||
"DOCUMENT": {
|
||||
"DOWNLOAD": "Download",
|
||||
"DELETE": "Delete",
|
||||
"SYSTEM_2": "System document action 2",
|
||||
"CUSTOM": "Custom action"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"DATATABLE": {
|
||||
"COLUMNS": {
|
||||
"DISPLAY_NAME": "Display name",
|
||||
"CREATED_BY": "Created by",
|
||||
"CREATED_ON": "Created on"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"FOLDER": {
|
||||
"SYSTEM_1": "System folder action 1",
|
||||
"CUSTOM": "Custom folder action"
|
||||
},
|
||||
"DOCUMENT": {
|
||||
"DOWNLOAD": "Download",
|
||||
"SYSTEM_2": "System document action 2",
|
||||
"CUSTOM": "Custom action"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"LOGIN": {
|
||||
"LOGO": "Alfresco",
|
||||
"LABEL": {
|
||||
"LOGIN": "Login",
|
||||
"USERNAME": "Username",
|
||||
"PASSWORD": "Password",
|
||||
"REMEMBER": "Remember"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"USERNAME-REQUIRED": "Required",
|
||||
"USERNAME-MIN": "Your username needs to be at least 4 characters.",
|
||||
"PASSWORD-REQUIRED": "Enter your password to sign in",
|
||||
"LOGIN-ERROR": "You have entered an invalid username or password",
|
||||
"LOGIN-SUCCESS": "Login successful"
|
||||
},
|
||||
"BUTTON": {
|
||||
"LOGIN": "Login"
|
||||
},
|
||||
"ACTION": {
|
||||
"HELP": "NEED HELP?",
|
||||
"REGISTER": "REGISTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,11 @@
|
||||
<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>
|
||||
|
||||
<!-- Stencils integration -->
|
||||
<script src="node_modules/ng2-activiti-form/stencils/runtime.ng1.js"></script>
|
||||
<script src="node_modules/ng2-activiti-form/stencils/runtime.adf.js"></script>
|
||||
<script src="http://localhost:9999/activiti-app/app/rest/script-files/controllers"></script>
|
||||
|
||||
<!-- 2. Configure SystemJS -->
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Alfresco-Angular2-Demo",
|
||||
"description": "Demo shell for Alfresco Angular2 components",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && rimraf dist node_modules typings",
|
||||
@@ -81,21 +81,21 @@
|
||||
"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",
|
||||
"alfresco-js-api": "^0.5.0",
|
||||
"ng2-activiti-analytics": "0.5.0",
|
||||
"ng2-alfresco-core": "0.5.0",
|
||||
"ng2-alfresco-datatable": "0.5.0",
|
||||
"ng2-alfresco-documentlist": "0.5.0",
|
||||
"ng2-alfresco-login": "0.5.0",
|
||||
"ng2-alfresco-search": "0.5.0",
|
||||
"ng2-alfresco-upload": "0.5.0",
|
||||
"ng2-alfresco-viewer": "0.5.0",
|
||||
"ng2-activiti-form": "0.5.0",
|
||||
"ng2-activiti-tasklist": "0.5.0",
|
||||
"ng2-alfresco-userinfo": "0.5.0",
|
||||
"ng2-activiti-processlist": "0.5.0",
|
||||
"ng2-alfresco-webscript": "0.5.0",
|
||||
"ng2-alfresco-tag": "0.5.0",
|
||||
"dialog-polyfill": "^0.4.3",
|
||||
"element.scrollintoviewifneeded-polyfill": "^1.0.1"
|
||||
},
|
||||
|
||||
@@ -56,10 +56,10 @@
|
||||
"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"
|
||||
"alfresco-js-api": "^0.5.0",
|
||||
"ng2-alfresco-core": "0.5.0",
|
||||
"ng2-activiti-diagrams": "0.5.0",
|
||||
"ng2-activiti-analytics": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.42",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-analytics",
|
||||
"description": "Activiti Angular2 Analytics Component",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
@@ -54,22 +54,16 @@
|
||||
"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",
|
||||
"alfresco-js-api": "^0.5.0",
|
||||
"ng2-translate": "2.5.0",
|
||||
"ng2-alfresco-core": "0.4.0",
|
||||
"ng2-activiti-diagrams": "0.4.0"
|
||||
"ng2-alfresco-core": "0.5.0",
|
||||
"ng2-activiti-diagrams": "0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.42",
|
||||
|
||||
@@ -45,7 +45,7 @@ export class AnalyticsReportHeatMapComponent implements OnInit {
|
||||
private analyticsService: AnalyticsService,
|
||||
private formBuilder: FormBuilder) {
|
||||
if (translate) {
|
||||
translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src');
|
||||
translate.addTranslationFolder('ng2-activiti-analytics', 'node_modules/ng2-activiti-analytics/dist/src');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges {
|
||||
private analyticsService: AnalyticsService,
|
||||
private formBuilder: FormBuilder ) {
|
||||
if (translate) {
|
||||
translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src');
|
||||
translate.addTranslationFolder('ng2-activiti-analytics', 'node_modules/ng2-activiti-analytics/dist/src');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export class AnalyticsComponent implements OnChanges {
|
||||
private analyticsService: AnalyticsService) {
|
||||
console.log('AnalyticsComponent');
|
||||
if (translate) {
|
||||
translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src');
|
||||
translate.addTranslationFolder('ng2-activiti-analytics', 'node_modules/ng2-activiti-analytics/dist/src');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
"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"
|
||||
"alfresco-js-api": "^0.5.0",
|
||||
"ng2-alfresco-core": "0.5.0",
|
||||
"ng2-activiti-diagrams": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.42",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-diagrams",
|
||||
"description": "Activiti Angular2 Diagrams Component",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
@@ -50,17 +50,11 @@
|
||||
"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"
|
||||
"alfresco-js-api": "^0.5.0",
|
||||
"ng2-alfresco-core": "0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.42",
|
||||
|
||||
@@ -54,7 +54,7 @@ export class DiagramComponent {
|
||||
private raphaelService: RaphaelService,
|
||||
private diagramsService: DiagramsService) {
|
||||
if (translate) {
|
||||
translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src');
|
||||
translate.addTranslationFolder('ng2-activiti-analytics', 'node_modules/ng2-activiti-analytics/dist/src');
|
||||
}
|
||||
this.elementRef = elementRef;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,6 @@ 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';
|
||||
@@ -150,8 +149,6 @@ export class AppModule {
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Display form instance by task id
|
||||
@@ -173,7 +170,7 @@ For an existing Task both form and values will be fetched and displayed.
|
||||
</activiti-form>
|
||||
```
|
||||
|
||||
Only form definition will be fetched
|
||||
Only form definition will be fetched.
|
||||
|
||||
### Display form definition by form name
|
||||
|
||||
@@ -184,8 +181,11 @@ Only form definition will be fetched
|
||||
</activiti-form>
|
||||
```
|
||||
|
||||
### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form. If there are no form
|
||||
definied in activiti for the type of the node, a new form will be automaticaly created in activiti.
|
||||
### Display form definition by ECM nodeId
|
||||
|
||||
In this case the metadata of the node are showed in an activiti Form.
|
||||
If there is no form definied in activiti for the type of the node,
|
||||
a new form will be automaticaly created in Activiti.
|
||||
|
||||
```html
|
||||
<activiti-form
|
||||
@@ -193,7 +193,9 @@ definied in activiti for the type of the node, a new form will be automaticaly c
|
||||
</activiti-form>
|
||||
```
|
||||
|
||||
### Display form definition by form name, and store the form field as metadata. The param nameNode is optional.
|
||||
### Display form definition by form name, and store the form field as metadata.
|
||||
|
||||
The param nameNode is optional.
|
||||
|
||||
```html
|
||||
<activiti-form
|
||||
@@ -204,8 +206,10 @@ definied in activiti for the type of the node, a new form will be automaticaly c
|
||||
</activiti-form>
|
||||
```
|
||||
|
||||
### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form, and store the form field
|
||||
as metadata. The param nameNode is optional.
|
||||
### Display form definition by ECM nodeId
|
||||
|
||||
In this case the metadata of the node are showed in an activiti Form,
|
||||
and store the form field as metadata. The param nameNode is optional.
|
||||
|
||||
```html
|
||||
<activiti-form
|
||||
@@ -324,6 +328,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.**
|
||||
|
||||
## See also
|
||||
|
||||
- [Form Stencils with Angular 2](docs/stencils.md)
|
||||
- [Form Extensibility and Customisation](docs/extensibility.md).
|
||||
|
||||
## Build from sources
|
||||
|
||||
Alternatively you can build component from sources with the following commands:
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
"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"
|
||||
"alfresco-js-api": "^0.5.0",
|
||||
"ng2-alfresco-core": "0.5.0",
|
||||
"ng2-activiti-form": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.42",
|
||||
|
||||
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,261 @@
|
||||
# Form Extensibility and Customisation
|
||||
|
||||
_Note: it is assumed you are familiar with Alfresco Activiti form definition structure._
|
||||
|
||||
- How components and widgets are rendred on a Form
|
||||
- Replacing default form widgets with custom components
|
||||
- Replacing custom stencils with custom components
|
||||
|
||||
|
||||
|
||||
## How components and widgets are rendred on a Form
|
||||
|
||||
All form field editors (aka widgets) on a Form are rendered by means of `FormFieldComponent`
|
||||
that takes an instance of a `FormFieldModel`:
|
||||
|
||||
```html
|
||||
<form-field [field]="field"></form-field>
|
||||
```
|
||||
|
||||
This component depends on `FormRenderingService` service to map `FormFieldModel` to UI component
|
||||
based on field type or metadata information.
|
||||
|
||||
### Component type resolvers
|
||||
|
||||
`FormRenderingService` maps field types to corresponding instances exposing `ComponentTypeResolver` interface:
|
||||
|
||||
```ts
|
||||
export interface ComponentTypeResolver {
|
||||
(field: FormFieldModel): Type<{}>;
|
||||
}
|
||||
```
|
||||
|
||||
Typically a `ComponentTypeResolver` is a function that takes `FormFieldModel` and returns corresponding component type.
|
||||
It can be either a predefined component type or a dynamically evaluated based on field properties and metadata.
|
||||
|
||||
#### Static component mapping
|
||||
|
||||
You can (re)map fields like following:
|
||||
|
||||
```ts
|
||||
let customResolver: ComponentTypeResolver = () => CustomWidgetComponent;
|
||||
formRenderingService.setComponentTypeResolver('text', customResolver, true);
|
||||
```
|
||||
|
||||
or simply:
|
||||
|
||||
```ts
|
||||
formRenderingService.setComponentTypeResolver('text', () => CustomWidgetComponent, true);
|
||||
```
|
||||
|
||||
#### Dynamic component mapping
|
||||
|
||||
Alternatively your resolver may return different component types based on `FormFieldModel` state and condition:
|
||||
|
||||
```ts
|
||||
let customResolver: ComponentTypeResolver = (field: FormFieldModel): Type<{}> => {
|
||||
if (field) {
|
||||
let params = field.params;
|
||||
}
|
||||
return UnknownWidget;
|
||||
};
|
||||
formRenderingService.setComponentTypeResolver('text', customResolver, true);
|
||||
```
|
||||
|
||||
### Default component mappings
|
||||
|
||||
Stencil Name | Field Type | Component Type |
|
||||
| --- | --- | --- |
|
||||
| Text | text | TextWidget |
|
||||
| Number | integer | NumberWidget |
|
||||
| Multi-line text | multi-line-text | MultilineTextWidget |
|
||||
| Checkbox | boolean | CheckboxWidget |
|
||||
| Dropdown | dropdown | DropdownWidget |
|
||||
| Date | date | DateWidget |
|
||||
| Amount | amount | AmountWidget |
|
||||
| Radio buttons | radio-buttons | RadioButtonsWidget |
|
||||
| Hyperlink | hyperlink | HyperlinkWidget |
|
||||
| Display value | readonly | DisplayValueWidget |
|
||||
| Display text | readonly-text | DisplayTextWidget |
|
||||
| Typeahead | typeahead | TypeaheadWidget |
|
||||
| People | people | PeopleWidget |
|
||||
| Group of people | functional-group | FunctionalGroupWidget |
|
||||
| Dynamic table | dynamic-table | DynamicTableWidget |
|
||||
| N/A | container | ContainerWidget (layout component) |
|
||||
| Header | group | ContainerWidget |
|
||||
| Attach | upload | AttachWidget or UploadWidget (based on metadata) |
|
||||
| N/A | N/A | UnknownWidget |
|
||||
|
||||
|
||||
|
||||
## Replacing default form widgets with custom components
|
||||
|
||||
This is a short walkthrough on replacing a standard `Text` widget with a custom component for all Alfresco Activiti forms
|
||||
rendered within `<activiti-form>` component.
|
||||
|
||||
First let's create a simple Alfresco Activiti form with `Text` widgets:
|
||||
|
||||

|
||||
|
||||
Every custom widget must inherit `WidgetComponent` class in order to function properly:
|
||||
|
||||
```ts
|
||||
import { Component } from '@angular/core';
|
||||
import { WidgetComponent } from 'ng2-activiti-form';
|
||||
|
||||
@Component({
|
||||
selector: 'custom-editor',
|
||||
template: `
|
||||
<div style="color: red">Look, I'm a custom editor!</div>
|
||||
`
|
||||
})
|
||||
export class CustomEditorComponent extends WidgetComponent {}
|
||||
```
|
||||
|
||||
Now you will need adding it to the application module or any custom module that is imported into the application one:
|
||||
|
||||
```ts
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CustomEditorComponent } from './custom-editor.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ CustomEditorComponent ],
|
||||
exports: [ CustomEditorComponent ],
|
||||
entryComponents: [ CustomEditorComponent ]
|
||||
})
|
||||
export class CustomEditorsModule {}
|
||||
```
|
||||
|
||||
Every custom widget should be added into all three module collections: `declarations`, `exports` and `entryComponents`.
|
||||
|
||||
If you decided storing custom widgets in a separate dedicated module (and optionally as separate redistributable library)
|
||||
don't forget importing it into your main application one:
|
||||
|
||||
```ts
|
||||
@NgModule({
|
||||
imports: [
|
||||
// ...
|
||||
CustomEditorsModule
|
||||
// ...
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
Now you can import `FormRenderingService` in any of your Views and override default mapping similar to the following:
|
||||
|
||||
```ts
|
||||
import { Component } from '@angular/core';
|
||||
import { CustomEditorComponent } from './custom-editor.component';
|
||||
|
||||
@Component({...})
|
||||
export class MyView {
|
||||
|
||||
constructor(formRenderingService: FormRenderingService) {
|
||||
formRenderingService.setComponentTypeResolver('text', () => CustomEditorComponent, true);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
At runtime it should look similar to the following:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## Replacing custom stencils with custom components
|
||||
|
||||
This is a short walkthrough on rendering custom Alfresco Activiti stencils by means of custom Angular 2 components.
|
||||
|
||||
### Creating custom stencil
|
||||
|
||||
First let's create a basic stencil and call it `Custom Stencil 01`:
|
||||
|
||||

|
||||
|
||||
_Note the `internal identifier` value as it will become a `field type` value when corresponding form is rendered._
|
||||
|
||||
Next put some simple html layout for `Form runtime template` and `Form editor template` fields:
|
||||
|
||||
```html
|
||||
<div style="color: blue">Custom activiti stencil</div>
|
||||
```
|
||||
|
||||
Now you are ready to design a test form based on your custom stencil:
|
||||
|
||||

|
||||
|
||||
Once wired with a new task it should look like the following within Alfresco Activiti web application:
|
||||
|
||||

|
||||
|
||||
### Creating custom widget
|
||||
|
||||
If you load previously created task into ADF `<actiiti-form>` component you will see something like the following:
|
||||
|
||||

|
||||
|
||||
Let's create an Angular 2 component to render missing content:
|
||||
|
||||
```ts
|
||||
import { Component } from '@angular/core';
|
||||
import { WidgetComponent } from 'ng2-activiti-form';
|
||||
|
||||
@Component({
|
||||
selector: 'custom-stencil-01',
|
||||
template: `<div style="color: green">ADF version of custom Activiti stencil</div>`
|
||||
})
|
||||
export class CustomStencil01 extends WidgetComponent {}
|
||||
```
|
||||
|
||||
Put it inside custom module:
|
||||
|
||||
```ts
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CustomStencil01 } from './custom-stencil-01.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ CustomStencil01 ],
|
||||
exports: [ CustomStencil01 ],
|
||||
entryComponents: [ CustomStencil01 ]
|
||||
})
|
||||
export class CustomEditorsModule {}
|
||||
```
|
||||
|
||||
And import into your Application Module
|
||||
|
||||
```ts
|
||||
@NgModule({
|
||||
imports: [
|
||||
// ...
|
||||
CustomEditorsModule
|
||||
// ...
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
Now you can import `FormRenderingService` in any of your Views and provide new mapping:
|
||||
|
||||
```ts
|
||||
import { Component } from '@angular/core';
|
||||
import { CustomStencil01 } from './custom-stencil-01.component';
|
||||
|
||||
@Component({...})
|
||||
export class MyView {
|
||||
|
||||
constructor(formRenderingService: FormRenderingService) {
|
||||
formRenderingService.setComponentTypeResolver('custom_stencil_01', () => CustomStencil01, true);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
At the runtime you should now see your custom Angular 2 component rendered in place of the stencils:
|
||||
|
||||

|
||||
@@ -0,0 +1,144 @@
|
||||
# Form Stencils with Angular 2
|
||||
|
||||
Form component provides basic support for custom stencils created with Activiti stencil editor.
|
||||
|
||||
## Installing
|
||||
|
||||
Configuring support for stencils requires the following configuration for your `index.html` file:
|
||||
|
||||
```html
|
||||
<!-- Stencils integration -->
|
||||
<script src="node_modules/ng2-activiti-form/stencils/runtime.ng1.js"></script>
|
||||
<script src="node_modules/ng2-activiti-form/stencils/runtime.adf.js"></script>
|
||||
<script src="http://<activiti-app-root>/app/rest/script-files/controllers"></script>
|
||||
```
|
||||
|
||||
Where `<activiti-app-root>` should be replaced with a valid url pointing to your Activiti installation, for example:
|
||||
|
||||
```html
|
||||
<script src="http://localhost:9999/activiti-app/app/rest/script-files/controllers"></script>
|
||||
```
|
||||
- `/app/rest/script-files/controllers`
|
||||
provides all stencil controllers stored within Activiti
|
||||
|
||||
- `runtime.ng1.js`
|
||||
provides a compatibility layer for controllers created with Angular 1
|
||||
(this is to avoid runtime errors when loading Angular 1 code into `<activiti-form>` component)
|
||||
|
||||
- `runtime.adf.js`
|
||||
provides API for stencil management and registration,
|
||||
i.e. mapping html templates with corresponding controller classes
|
||||
|
||||
## Creating new stencil
|
||||
|
||||
Create a new stencil and add a new item called `ng2 component 01`.
|
||||
|
||||
The internal identifier in this case should be `ng2_component_01`.
|
||||
This value will be used as field type when form gets rendered.
|
||||
|
||||
## Form runtime template
|
||||
|
||||
This should be a valid Angular 2 component template that you want to render in `<activiti-form>` component:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<div>Angular2 Component</div>
|
||||
<div>Created by: {{name}}</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Form editor template
|
||||
|
||||
This can be any html layout to be rendered as a component placeholder in Activiti Form Designer.
|
||||
|
||||
```html
|
||||
<div>
|
||||
<div style="color: blue">
|
||||
Angular2 Component 01
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Custom component controller
|
||||
|
||||
This field should contain JavaScript code for Angular 2 component class.
|
||||
|
||||
_Note: If you are using TypeScript then you should be putting transpiled JavaScript code here,
|
||||
you can try official [TypeScript playground](http://www.typescriptlang.org/play/)
|
||||
to see how TS code gets transpiled into JS._
|
||||
|
||||
### JavaScript code
|
||||
|
||||
```js
|
||||
var SampleClass1 = (function () {
|
||||
function SampleClass1() {
|
||||
this.name = 'Denys';
|
||||
console.log('ng2_component_01 ctor');
|
||||
}
|
||||
SampleClass1.prototype.ngOnInit = function () {
|
||||
console.log('OnInit called');
|
||||
};
|
||||
return SampleClass1;
|
||||
}());
|
||||
```
|
||||
|
||||
### TypeScript code
|
||||
|
||||
The TypeScript version of the code above is:
|
||||
|
||||
```ts
|
||||
import { OnInit } from '@angular/core';
|
||||
|
||||
class SampleClass1 implements OnInit {
|
||||
|
||||
constructor() {
|
||||
console.log('ctor called');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
console.log('OnInit called');
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Mapping template with component class
|
||||
|
||||
In order to map **form runtime template** with the corresponding component class
|
||||
you will need registering both parts with `adf.registerComponent(identifier, class)` api:
|
||||
|
||||
```js
|
||||
if (adf) {
|
||||
adf.registerComponent('ng2_component_01', SampleClass1);
|
||||
}
|
||||
```
|
||||
|
||||
### Final result
|
||||
|
||||
```js
|
||||
var SampleClass1 = (function () {
|
||||
function SampleClass1() {
|
||||
this.name = 'Denys';
|
||||
console.log('ng2_component_01 ctor');
|
||||
}
|
||||
SampleClass1.prototype.ngOnInit = function () {
|
||||
console.log('OnInit called');
|
||||
};
|
||||
return SampleClass1;
|
||||
}());
|
||||
|
||||
if (adf) {
|
||||
adf.registerComponent('ng2_component_01', SampleClass1);
|
||||
}
|
||||
```
|
||||
|
||||
## Runtime result
|
||||
|
||||
When rendered on the form this stencil item should look like the following:
|
||||
|
||||
```html
|
||||
Angular2 Component
|
||||
Created by: Denys
|
||||
```
|
||||
|
||||
ADF Form component will automatically assemble and compile a valid Angular 2 component on the fly.
|
||||
@@ -19,12 +19,14 @@ import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
|
||||
import { ActivitiForm } from './src/components/activiti-form.component';
|
||||
import { FormFieldComponent } from './src/components/form-field/form-field.component';
|
||||
import { ActivitiStartForm } from './src/components/activiti-start-form.component';
|
||||
import { FormService } from './src/services/form.service';
|
||||
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 { FormRenderingService } from './src/services/form-rendering.service';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { WIDGET_DIRECTIVES } from './src/components/widgets/index';
|
||||
|
||||
@@ -34,10 +36,12 @@ 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 * from './src/services/form-rendering.service';
|
||||
|
||||
export const ACTIVITI_FORM_DIRECTIVES: any[] = [
|
||||
ActivitiForm,
|
||||
ActivitiStartForm,
|
||||
FormFieldComponent,
|
||||
...WIDGET_DIRECTIVES
|
||||
];
|
||||
|
||||
@@ -46,7 +50,8 @@ export const ACTIVITI_FORM_PROVIDERS: any[] = [
|
||||
EcmModelService,
|
||||
NodeService,
|
||||
WidgetVisibilityService,
|
||||
ActivitiAlfrescoContentService
|
||||
ActivitiAlfrescoContentService,
|
||||
FormRenderingService
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -57,6 +62,9 @@ export const ACTIVITI_FORM_PROVIDERS: any[] = [
|
||||
declarations: [
|
||||
...ACTIVITI_FORM_DIRECTIVES
|
||||
],
|
||||
entryComponents: [
|
||||
...WIDGET_DIRECTIVES
|
||||
],
|
||||
providers: [
|
||||
...ACTIVITI_FORM_PROVIDERS
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ng2-activiti-form",
|
||||
"description": "Alfresco Activiti Form Component for Angular 2",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"author": "Alfresco Software, Ltd.",
|
||||
"scripts": {
|
||||
"clean": "npm install rimraf && rimraf dist node_modules typings",
|
||||
@@ -58,18 +58,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"
|
||||
"alfresco-js-api": "^0.5.0",
|
||||
"ng2-alfresco-core": "0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.42",
|
||||
|
||||
@@ -15,23 +15,7 @@
|
||||
|
||||
<div *ngIf="!form.hasTabs() && form.hasFields()">
|
||||
<div *ngFor="let field of form.fields">
|
||||
<div [ngSwitch]="field.type">
|
||||
<div *ngSwitchCase="'container'">
|
||||
<container-widget [content]="field" (formValueChanged)="checkVisibility($event)"></container-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'group'">
|
||||
<container-widget [content]="field" (formValueChanged)="checkVisibility($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)="checkVisibility($event)"></display-value-widget>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
<span>UNKNOWN WIDGET TYPE: {{field.type}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<form-field [field]="field.field"></form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,23 +12,7 @@
|
||||
|
||||
<div *ngIf="!form.hasTabs() && form.hasFields()">
|
||||
<div *ngFor="let field of form.fields">
|
||||
<div [ngSwitch]="field.type">
|
||||
<div *ngSwitchCase="'container'">
|
||||
<container-widget [content]="field" (formValueChanged)="checkVisibility($event)"></container-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'group'">
|
||||
<container-widget [content]="field" (formValueChanged)="checkVisibility($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)="checkVisibility($event)"></display-value-widget>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
<span>UNKNOWN WIDGET TYPE: {{field.type}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<form-field [field]="field.field"></form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
import { ActivitiStartForm } from './activiti-start-form.component';
|
||||
import { FormFieldComponent } from './form-field/form-field.component';
|
||||
import { WIDGET_DIRECTIVES } from './widgets/index';
|
||||
import { FormService } from './../services/form.service';
|
||||
import { EcmModelService } from './../services/ecm-model.service';
|
||||
@@ -43,6 +44,7 @@ describe('ActivitiStartForm', () => {
|
||||
imports: [ CoreModule ],
|
||||
declarations: [
|
||||
ActivitiStartForm,
|
||||
FormFieldComponent,
|
||||
...WIDGET_DIRECTIVES
|
||||
],
|
||||
providers: [
|
||||
|
||||
@@ -83,7 +83,7 @@ export class ActivitiStartForm extends ActivitiForm implements OnInit, AfterView
|
||||
}
|
||||
|
||||
if (this.translate) {
|
||||
this.translate.addTranslationFolder('node_modules/ng2-activiti-form/src');
|
||||
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/dist/src');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 { CoreModule } from 'ng2-alfresco-core';
|
||||
import { ActivitiFormModule } from './../../../index';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { FormFieldComponent } from './form-field.component';
|
||||
import { FormRenderingService } from './../../services/form-rendering.service';
|
||||
import { FormModel, FormFieldModel, FormFieldTypes } from './../widgets/core/index';
|
||||
import { TextWidget, CheckboxWidget } from './../widgets/index';
|
||||
|
||||
describe('FormFieldComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<FormFieldComponent>;
|
||||
let component: FormFieldComponent;
|
||||
let componentHandler: any;
|
||||
let form: FormModel;
|
||||
|
||||
let formRenderingService: FormRenderingService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ CoreModule, ActivitiFormModule ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', [
|
||||
'upgradeAllRegistered',
|
||||
'upgradeElement'
|
||||
]);
|
||||
window['componentHandler'] = componentHandler;
|
||||
|
||||
fixture = TestBed.createComponent(FormFieldComponent);
|
||||
component = fixture.componentInstance;
|
||||
formRenderingService = fixture.debugElement.injector.get(FormRenderingService);
|
||||
form = new FormModel();
|
||||
});
|
||||
|
||||
it('should create default component instance', () => {
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
|
||||
component.field = field;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.componentRef).toBeDefined();
|
||||
expect(component.componentRef.componentType).toBe(TextWidget);
|
||||
});
|
||||
|
||||
it('should create custom component instance', () => {
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
|
||||
formRenderingService.setComponentTypeResolver(FormFieldTypes.TEXT, () => CheckboxWidget, true);
|
||||
component.field = field;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.componentRef).toBeDefined();
|
||||
expect(component.componentRef.componentType).toBe(CheckboxWidget);
|
||||
});
|
||||
|
||||
it('should require field to create component', () => {
|
||||
component.field = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.componentRef).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should require component type to be resolved', () => {
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT
|
||||
});
|
||||
|
||||
spyOn(formRenderingService, 'resolveComponentType').and.returnValue(null);
|
||||
component.field = field;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(formRenderingService.resolveComponentType).toHaveBeenCalled();
|
||||
expect(component.componentRef).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit, OnDestroy,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
Input,
|
||||
ComponentRef,
|
||||
ComponentFactoryResolver,
|
||||
ComponentFactory,
|
||||
Compiler,
|
||||
NgModule,
|
||||
ModuleWithComponentFactories
|
||||
} from '@angular/core';
|
||||
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
|
||||
import { WidgetVisibilityService } from './../../services/widget-visibility.service';
|
||||
import { FormRenderingService } from './../../services/form-rendering.service';
|
||||
import { WidgetComponent } from './../widgets/widget.component';
|
||||
import { FormFieldModel } from './../widgets/core/index';
|
||||
|
||||
declare var adf: any;
|
||||
|
||||
@Component({
|
||||
selector: 'form-field',
|
||||
template: `
|
||||
<div [hidden]="!field?.isVisible">
|
||||
<div #container></div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class FormFieldComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('container', { read: ViewContainerRef })
|
||||
container: ViewContainerRef;
|
||||
|
||||
@Input()
|
||||
field: FormFieldModel = null;
|
||||
|
||||
componentRef: ComponentRef<{}>;
|
||||
|
||||
constructor(
|
||||
private formRenderingService: FormRenderingService,
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private visibilityService: WidgetVisibilityService,
|
||||
private compiler: Compiler) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
let customTemplate = this.field.form.customFieldTemplates[this.field.type];
|
||||
if (customTemplate && this.hasController(this.field.type)) {
|
||||
let factory = this.getComponentFactorySync(this.field.type, customTemplate);
|
||||
this.componentRef = this.container.createComponent(factory);
|
||||
let instance: any = this.componentRef.instance;
|
||||
if (instance) {
|
||||
instance.field = this.field;
|
||||
}
|
||||
} else {
|
||||
let componentType = this.formRenderingService.resolveComponentType(this.field);
|
||||
if (componentType) {
|
||||
let factory = this.componentFactoryResolver.resolveComponentFactory(componentType);
|
||||
this.componentRef = this.container.createComponent(factory);
|
||||
let instance = <WidgetComponent>this.componentRef.instance;
|
||||
instance.field = this.field;
|
||||
instance.fieldChanged.subscribe(field => {
|
||||
console.log('WidgetComponent.fieldChanged was used only to trigger visibility engine, components should do that internally if needed');
|
||||
if (field && field.form) {
|
||||
this.visibilityService.refreshVisibility(field.form);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.componentRef) {
|
||||
this.componentRef.destroy();
|
||||
this.componentRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
private hasController(type: string): boolean {
|
||||
return (adf && adf.components && adf.components[type]);
|
||||
}
|
||||
|
||||
private getComponentFactorySync(type: string, template: string): ComponentFactory<any> {
|
||||
let componentInfo = adf.components[type];
|
||||
|
||||
if (componentInfo.factory) {
|
||||
return componentInfo.factory;
|
||||
}
|
||||
|
||||
let metadata = {
|
||||
selector: `runtime-component-${type}`,
|
||||
template: template
|
||||
};
|
||||
|
||||
let factory = this.createComponentFactorySync(this.compiler, metadata, componentInfo.class);
|
||||
componentInfo.factory = factory;
|
||||
return factory;
|
||||
}
|
||||
|
||||
private createComponentFactorySync(compiler: Compiler, metadata: Component, componentClass: any): ComponentFactory<any> {
|
||||
const cmpClass = componentClass || class RuntimeComponent { };
|
||||
const decoratedCmp = Component(metadata)(cmpClass);
|
||||
|
||||
@NgModule({ imports: [CoreModule], declarations: [decoratedCmp] })
|
||||
class RuntimeComponentModule { }
|
||||
|
||||
let module: ModuleWithComponentFactories<any> = compiler.compileModuleAndAllComponentsSync(RuntimeComponentModule);
|
||||
return module.componentFactories.find(x => x.componentType === decoratedCmp);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label amount-widget"
|
||||
<div class="mdl-textfield mdl-js-textfield amount-widget"
|
||||
[class.amount-widget__invalid]="!field.isValid">
|
||||
<label [attr.for]="field.id">{{field.name}}, {{currency}}</label>
|
||||
<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}}, {{currency}}</label>
|
||||
[disabled]="field.readOnly"
|
||||
placeholder="{{field.placeholder}}">
|
||||
<span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('AmountWidget', () => {
|
||||
let widget: AmountWidget;
|
||||
|
||||
beforeEach(() => {
|
||||
widget = new AmountWidget(null);
|
||||
widget = new AmountWidget();
|
||||
});
|
||||
|
||||
it('should setup currentcy from field', () => {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ElementRef, OnInit } from '@angular/core';
|
||||
import { TextFieldWidgetComponent } from './../textfield-widget.component';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -24,16 +24,12 @@ import { TextFieldWidgetComponent } from './../textfield-widget.component';
|
||||
templateUrl: './amount.widget.html',
|
||||
styleUrls: ['./amount.widget.css']
|
||||
})
|
||||
export class AmountWidget extends TextFieldWidgetComponent implements OnInit {
|
||||
export class AmountWidget extends WidgetComponent 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;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<label [attr.for]="field.id">{{field.name}}</label>
|
||||
<div>
|
||||
<span *ngIf="hasFile" class="attach-widget__file">{{getLinkedFileName()}}</span>
|
||||
<span *ngIf="hasFile()" class="attach-widget__file mdl-chip"><span class="mdl-chip__text">{{getLinkedFileName()}}</span></span>
|
||||
<button #browseFile (click)="showDialog();" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__browser">
|
||||
<i class="material-icons">image</i>
|
||||
Browse {{selectedFolderSiteName}}
|
||||
|
||||
@@ -42,24 +42,23 @@ describe('AttachWidget', () => {
|
||||
});
|
||||
|
||||
it('should require field value to check file', () => {
|
||||
widget.hasFile = false;
|
||||
widget.field = null;
|
||||
widget.ngOnInit();
|
||||
expect(widget.hasFile).toBeFalsy();
|
||||
expect(widget.hasFile()).toBeFalsy();
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: null
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.hasFile).toBeFalsy();
|
||||
expect(widget.hasFile()).toBeFalsy();
|
||||
|
||||
widget.field = new FormFieldModel(null, {
|
||||
type: FormFieldTypes.UPLOAD,
|
||||
value: [{ name: 'file' }]
|
||||
});
|
||||
widget.ngOnInit();
|
||||
expect(widget.hasFile).toBeTruthy();
|
||||
expect(widget.hasFile()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setup with form field', () => {
|
||||
@@ -116,19 +115,20 @@ describe('AttachWidget', () => {
|
||||
expect(widget.selectedFile).toBe(node);
|
||||
expect(widget.field.value).toEqual([link]);
|
||||
expect(widget.field.json.value).toEqual([link]);
|
||||
expect(widget.hasFile()).toBeTruthy();
|
||||
});
|
||||
|
||||
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.hasFile()).toBeFalsy();
|
||||
expect(widget.field.value).toBeNull();
|
||||
expect(widget.field.json.value).toBeNull();
|
||||
expect(widget.hasFile()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should close dialog on cancel', () => {
|
||||
|
||||
@@ -35,7 +35,6 @@ export class AttachWidget extends WidgetComponent implements OnInit {
|
||||
selectedFolderSiteName: string;
|
||||
selectedFolderAccountId: string;
|
||||
fileName: string;
|
||||
hasFile: boolean;
|
||||
selectedFolderNodes: [ExternalContent];
|
||||
selectedFile: ExternalContent;
|
||||
|
||||
@@ -54,10 +53,6 @@ export class AttachWidget extends WidgetComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
if (this.field.value) {
|
||||
this.hasFile = true;
|
||||
}
|
||||
|
||||
let params = this.field.params;
|
||||
|
||||
if (params &&
|
||||
@@ -149,7 +144,10 @@ export class AttachWidget extends WidgetComponent implements OnInit {
|
||||
reset() {
|
||||
this.field.value = null;
|
||||
this.field.json.value = null;
|
||||
this.hasFile = false;
|
||||
}
|
||||
|
||||
hasFile(): boolean {
|
||||
return this.field && this.field.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="mdl-checkbox__input"
|
||||
[checked]="field.value"
|
||||
[(ngModel)]="field.value"
|
||||
(ngModelChange)="checkVisibility(field)"
|
||||
(ngModelChange)="onChange()"
|
||||
[disabled]="field.readOnly">
|
||||
<span class="mdl-checkbox__label">{{field.name}}</span>
|
||||
</label>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -25,4 +26,12 @@ import { WidgetComponent } from './../widget.component';
|
||||
})
|
||||
export class CheckboxWidget extends WidgetComponent {
|
||||
|
||||
constructor(private visibilityService: WidgetVisibilityService) {
|
||||
super();
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContainerColumnModel } from './container-column.model';
|
||||
import { FormModel } from './form.model';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { ContainerColumnModel } from './../core/container-column.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
|
||||
describe('ContainerColumnModel', () => {
|
||||
|
||||
@@ -15,57 +15,7 @@
|
||||
<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">
|
||||
<div [ngSwitch]="field.type" [hidden]="!field.isVisible">
|
||||
<div *ngSwitchCase="'integer'">
|
||||
<number-widget [field]="field" (fieldChanged)="fieldChanged($event);"></number-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'text'">
|
||||
<text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></text-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'multi-line-text'">
|
||||
<multiline-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></multiline-text-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'boolean'">
|
||||
<checkbox-widget [field]="field" (fieldChanged)="fieldChanged($event);"></checkbox-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'dropdown'">
|
||||
<dropdown-widget [field]="field" (fieldChanged)="fieldChanged($event);"></dropdown-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'hyperlink'">
|
||||
<hyperlink-widget [field]="field" (fieldChanged)="fieldChanged($event);"></hyperlink-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'radio-buttons'">
|
||||
<radio-buttons-widget [field]="field" (fieldChanged)="fieldChanged($event);"></radio-buttons-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'readonly'">
|
||||
<display-value-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-value-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'readonly-text'">
|
||||
<display-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-text-widget>
|
||||
</div>
|
||||
<div *ngSwitchCase="'upload'">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<form-field [field]="field"></form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 { ContainerWidgetModel } from './container.widget.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormFieldTypes } from './../core/form-field-types';
|
||||
|
||||
describe('ContainerWidgetModel', () => {
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form);
|
||||
let model = new ContainerWidgetModel(field);
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
it('should be expanded by default', () => {
|
||||
let container = new ContainerWidgetModel(null);
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should wrap fields into columns on setup', () => {
|
||||
let form = new FormModel();
|
||||
|
||||
let json = {
|
||||
fieldType: '<type>',
|
||||
id: '<id>',
|
||||
name: '<name>',
|
||||
type: '<type>',
|
||||
tab: '<tab>',
|
||||
numberOfColumns: 3,
|
||||
params: {},
|
||||
visibilityCondition: {},
|
||||
fields: {
|
||||
'1': [
|
||||
{ id: 'field-1' },
|
||||
{ id: 'field-3' }
|
||||
],
|
||||
'2': [
|
||||
{ id: 'field-2' }
|
||||
],
|
||||
'3': null
|
||||
}
|
||||
};
|
||||
|
||||
let field = new FormFieldModel(form, json);
|
||||
|
||||
let container = new ContainerWidgetModel(field);
|
||||
expect(container.columns.length).toBe(3);
|
||||
|
||||
let col1 = container.columns[0];
|
||||
expect(col1.fields.length).toBe(2);
|
||||
expect(col1.fields[0].id).toBe('field-1');
|
||||
expect(col1.fields[1].id).toBe('field-3');
|
||||
|
||||
let col2 = container.columns[1];
|
||||
expect(col2.fields.length).toBe(1);
|
||||
expect(col2.fields[0].id).toBe('field-2');
|
||||
|
||||
let col3 = container.columns[2];
|
||||
expect(col3.fields.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should allow collapsing only when of a group type', () => {
|
||||
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.CONTAINER,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
|
||||
expect(container.isCollapsible()).toBeFalsy();
|
||||
container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
expect(container.isCollapsible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow collapsing only when explicitly defined in params', () => {
|
||||
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {}
|
||||
}));
|
||||
expect(container.isCollapsible()).toBeFalsy();
|
||||
|
||||
container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
}));
|
||||
expect(container.isCollapsible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be collapsed by default', () => {
|
||||
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true,
|
||||
collapseByDefault: true
|
||||
}
|
||||
}));
|
||||
expect(container.isCollapsedByDefault()).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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 { ContainerModel } from './../core/container.model';
|
||||
import { ContainerColumnModel } from './../core/container-column.model';
|
||||
import { FormFieldTypes } from './../core/form-field-types';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
|
||||
export class ContainerWidgetModel extends ContainerModel {
|
||||
|
||||
columns: ContainerColumnModel[] = [];
|
||||
isExpanded: boolean = true;
|
||||
|
||||
isGroup(): boolean {
|
||||
return this.type === FormFieldTypes.GROUP;
|
||||
}
|
||||
|
||||
isCollapsible(): boolean {
|
||||
let allowCollapse = false;
|
||||
|
||||
if (this.isGroup() && this.field.params['allowCollapse']) {
|
||||
allowCollapse = <boolean> this.field.params['allowCollapse'];
|
||||
}
|
||||
|
||||
return allowCollapse;
|
||||
}
|
||||
|
||||
isCollapsedByDefault(): boolean {
|
||||
let collapseByDefault = false;
|
||||
|
||||
if (this.isCollapsible() && this.field.params['collapseByDefault']) {
|
||||
collapseByDefault = <boolean> this.field.params['collapseByDefault'];
|
||||
}
|
||||
|
||||
return collapseByDefault;
|
||||
}
|
||||
|
||||
constructor(field: FormFieldModel) {
|
||||
super(field);
|
||||
|
||||
if (this.field) {
|
||||
this.columns = this.field.columns || [];
|
||||
this.isExpanded = !this.isCollapsedByDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,14 @@
|
||||
*/
|
||||
|
||||
import { ContainerWidget } from './container.widget';
|
||||
import { ContainerWidgetModel } from './container.widget.model';
|
||||
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 { FormFieldComponent } from './../../form-field/form-field.component';
|
||||
import { fakeFormJson } from '../../../services/assets/widget-visibility.service.mock';
|
||||
|
||||
describe('ContainerWidget', () => {
|
||||
@@ -37,6 +38,15 @@ describe('ContainerWidget', () => {
|
||||
window['componentHandler'] = componentHandler;
|
||||
});
|
||||
|
||||
it('should wrap field with model instance', () => {
|
||||
let field = new FormFieldModel(null);
|
||||
let container = new ContainerWidget();
|
||||
container.field = field;
|
||||
container.ngOnInit();
|
||||
expect(container.content).toBeDefined();
|
||||
expect(container.content.field).toBe(field);
|
||||
});
|
||||
|
||||
it('should upgrade MDL content on view init', () => {
|
||||
let container = new ContainerWidget();
|
||||
container.ngAfterViewInit();
|
||||
@@ -52,12 +62,12 @@ describe('ContainerWidget', () => {
|
||||
});
|
||||
|
||||
it('should toggle underlying group container', () => {
|
||||
let container = new ContainerModel(new FormModel(), {
|
||||
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
let widget = new ContainerWidget();
|
||||
widget.content = container;
|
||||
@@ -70,9 +80,9 @@ describe('ContainerWidget', () => {
|
||||
});
|
||||
|
||||
it('should toggle only collapsible container', () => {
|
||||
let container = new ContainerModel(new FormModel(), {
|
||||
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP
|
||||
});
|
||||
}));
|
||||
|
||||
let widget = new ContainerWidget();
|
||||
widget.content = container;
|
||||
@@ -83,12 +93,12 @@ describe('ContainerWidget', () => {
|
||||
});
|
||||
|
||||
it('should toggle only group container', () => {
|
||||
let container = new ContainerModel(new FormModel(), {
|
||||
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.CONTAINER,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
let widget = new ContainerWidget();
|
||||
widget.content = container;
|
||||
@@ -102,27 +112,27 @@ describe('ContainerWidget', () => {
|
||||
let widget = new ContainerWidget();
|
||||
let fakeForm = new FormModel();
|
||||
let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'});
|
||||
widget.formValueChanged.subscribe(field => {
|
||||
widget.fieldChanged.subscribe(field => {
|
||||
expect(field).not.toBe(null);
|
||||
expect(field.id).toBe('fakeField');
|
||||
expect(field.value).toBe('fakeValue');
|
||||
done();
|
||||
});
|
||||
|
||||
widget.fieldChanged(fakeField);
|
||||
widget.onFieldChanged(fakeField);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let containerWidgetComponent: ContainerWidget;
|
||||
let fixture: ComponentFixture<ContainerWidget>;
|
||||
let element: HTMLElement;
|
||||
let fakeContainerVisible: ContainerModel;
|
||||
let fakeContainerInvisible: ContainerModel;
|
||||
let fakeContainerVisible: ContainerWidgetModel;
|
||||
let fakeContainerInvisible: ContainerWidgetModel;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [WIDGET_DIRECTIVES]
|
||||
declarations: [FormFieldComponent, WIDGET_DIRECTIVES]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(ContainerWidget);
|
||||
containerWidgetComponent = fixture.componentInstance;
|
||||
@@ -133,18 +143,18 @@ describe('ContainerWidget', () => {
|
||||
beforeEach(() => {
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
|
||||
window['componentHandler'] = componentHandler;
|
||||
fakeContainerVisible = new ContainerModel(new FormModel(fakeFormJson), {
|
||||
fakeContainerVisible = new ContainerWidgetModel(new FormFieldModel(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), {
|
||||
}));
|
||||
fakeContainerInvisible = new ContainerWidgetModel(new FormFieldModel(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;
|
||||
});
|
||||
@@ -179,7 +189,7 @@ describe('ContainerWidget', () => {
|
||||
it('should hide header when it becomes not visible', async(() => {
|
||||
containerWidgetComponent.content = fakeContainerVisible;
|
||||
fixture.detectChanges();
|
||||
containerWidgetComponent.formValueChanged.subscribe((res) => {
|
||||
containerWidgetComponent.fieldChanged.subscribe((res) => {
|
||||
containerWidgetComponent.content.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
@@ -188,12 +198,12 @@ describe('ContainerWidget', () => {
|
||||
expect(element.querySelector('#container-header-label')).toBeNull();
|
||||
});
|
||||
});
|
||||
containerWidgetComponent.fieldChanged(null);
|
||||
containerWidgetComponent.onFieldChanged(null);
|
||||
}));
|
||||
|
||||
it('should show header when it becomes visible', async(() => {
|
||||
containerWidgetComponent.content = fakeContainerInvisible;
|
||||
containerWidgetComponent.formValueChanged.subscribe((res) => {
|
||||
containerWidgetComponent.fieldChanged.subscribe((res) => {
|
||||
containerWidgetComponent.content.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
@@ -204,7 +214,7 @@ describe('ContainerWidget', () => {
|
||||
expect(element.querySelector('#container-header-label').innerHTML).toContain('fake-cont-2-name');
|
||||
});
|
||||
});
|
||||
containerWidgetComponent.fieldChanged(null);
|
||||
containerWidgetComponent.onFieldChanged(null);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input, AfterViewInit, Output, EventEmitter } from '@angular/core';
|
||||
import { ContainerModel, FormFieldModel } from './../core/index';
|
||||
import { Component, AfterViewInit, OnInit } from '@angular/core';
|
||||
import { ContainerWidgetModel } from './container.widget.model';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -24,13 +25,9 @@ import { ContainerModel, FormFieldModel } from './../core/index';
|
||||
templateUrl: './container.widget.html',
|
||||
styleUrls: ['./container.widget.css']
|
||||
})
|
||||
export class ContainerWidget implements AfterViewInit {
|
||||
export class ContainerWidget extends WidgetComponent implements OnInit, AfterViewInit {
|
||||
|
||||
@Input()
|
||||
content: ContainerModel;
|
||||
|
||||
@Output()
|
||||
formValueChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
|
||||
content: ContainerWidgetModel;
|
||||
|
||||
onExpanderClicked() {
|
||||
if (this.content && this.content.isCollapsible()) {
|
||||
@@ -38,6 +35,12 @@ export class ContainerWidget implements AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
this.content = new ContainerWidgetModel(this.field);
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.setupMaterialComponents();
|
||||
}
|
||||
@@ -50,9 +53,4 @@ export class ContainerWidget implements AfterViewInit {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fieldChanged(field: FormFieldModel) {
|
||||
this.formValueChanged.emit(field);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,129 +17,14 @@
|
||||
|
||||
import { ContainerModel } from './container.model';
|
||||
import { FormModel } from './form.model';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
|
||||
describe('ContainerModel', () => {
|
||||
|
||||
it('should store the form reference', () => {
|
||||
let form = new FormModel();
|
||||
let model = new ContainerModel(form);
|
||||
let model = new ContainerModel(new FormFieldModel(form));
|
||||
expect(model.form).toBe(form);
|
||||
});
|
||||
|
||||
it('should store original json', () => {
|
||||
let json = {};
|
||||
let model = new ContainerModel(null, json);
|
||||
expect(model.json).toBe(json);
|
||||
});
|
||||
|
||||
it('should have 1 column layout by default', () => {
|
||||
let container = new ContainerModel(null, null);
|
||||
expect(container.numberOfColumns).toBe(1);
|
||||
});
|
||||
|
||||
it('should be expanded by default', () => {
|
||||
let container = new ContainerModel(null, null);
|
||||
expect(container.isExpanded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setup with json config', () => {
|
||||
let json = {
|
||||
fieldType: '<type>',
|
||||
id: '<id>',
|
||||
name: '<name>',
|
||||
type: '<type>',
|
||||
tab: '<tab>',
|
||||
numberOfColumns: 2,
|
||||
params: {}
|
||||
};
|
||||
let container = new ContainerModel(null, json);
|
||||
Object.keys(json).forEach(key => {
|
||||
expect(container[key]).toEqual(json[key]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should wrap fields into columns on setup', () => {
|
||||
let form = new FormModel();
|
||||
let json = {
|
||||
fieldType: '<type>',
|
||||
id: '<id>',
|
||||
name: '<name>',
|
||||
type: '<type>',
|
||||
tab: '<tab>',
|
||||
numberOfColumns: 3,
|
||||
params: {},
|
||||
visibilityCondition: {},
|
||||
fields: {
|
||||
'1': [
|
||||
{ id: 'field-1' },
|
||||
{ id: 'field-3' }
|
||||
],
|
||||
'2': [
|
||||
{ id: 'field-2' }
|
||||
],
|
||||
'3': null
|
||||
}
|
||||
};
|
||||
let container = new ContainerModel(form, json);
|
||||
expect(container.columns.length).toBe(3);
|
||||
|
||||
let col1 = container.columns[0];
|
||||
expect(col1.fields.length).toBe(2);
|
||||
expect(col1.fields[0].id).toBe('field-1');
|
||||
expect(col1.fields[1].id).toBe('field-3');
|
||||
|
||||
let col2 = container.columns[1];
|
||||
expect(col2.fields.length).toBe(1);
|
||||
expect(col2.fields[0].id).toBe('field-2');
|
||||
|
||||
let col3 = container.columns[2];
|
||||
expect(col3.fields.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should allow collapsing only when of a group type', () => {
|
||||
let container = new ContainerModel(new FormModel(), {
|
||||
type: FormFieldTypes.CONTAINER,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(container.isCollapsible()).toBeFalsy();
|
||||
container = new ContainerModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
});
|
||||
expect(container.isCollapsible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow collapsing only when explicitly defined in params', () => {
|
||||
let container = new ContainerModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {}
|
||||
});
|
||||
expect(container.isCollapsible()).toBeFalsy();
|
||||
|
||||
container = new ContainerModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true
|
||||
}
|
||||
});
|
||||
expect(container.isCollapsible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be collapsed by default', () => {
|
||||
let container = new ContainerModel(new FormModel(), {
|
||||
type: FormFieldTypes.GROUP,
|
||||
params: {
|
||||
allowCollapse: true,
|
||||
collapseByDefault: true
|
||||
}
|
||||
});
|
||||
expect(container.isCollapsedByDefault()).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -16,95 +16,21 @@
|
||||
*/
|
||||
|
||||
import { FormWidgetModel } from './form-widget.model';
|
||||
import { FormFieldMetadata } from './form-field-metadata';
|
||||
import { ContainerColumnModel } from './container-column.model';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormModel } from './form.model';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
|
||||
export class ContainerModel extends FormWidgetModel {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
isCollapsible(): boolean {
|
||||
let allowCollapse = false;
|
||||
|
||||
if (this.isGroup() && this.params['allowCollapse']) {
|
||||
allowCollapse = <boolean> this.params['allowCollapse'];
|
||||
}
|
||||
|
||||
return allowCollapse;
|
||||
}
|
||||
|
||||
isCollapsedByDefault(): boolean {
|
||||
let collapseByDefault = false;
|
||||
|
||||
if (this.isCollapsible() && this.params['collapseByDefault']) {
|
||||
collapseByDefault = <boolean> this.params['collapseByDefault'];
|
||||
}
|
||||
|
||||
return collapseByDefault;
|
||||
}
|
||||
|
||||
constructor(form: FormModel, json?: any) {
|
||||
super(form, json);
|
||||
|
||||
if (json) {
|
||||
this.field = new FormFieldModel(form, json);
|
||||
this.numberOfColumns = <number> json.numberOfColumns;
|
||||
this.params = <FormFieldMetadata> json.params || {};
|
||||
|
||||
let columnSize: number = 12;
|
||||
if (this.numberOfColumns > 1) {
|
||||
columnSize = 12 / this.numberOfColumns;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.numberOfColumns; i++) {
|
||||
let col = new ContainerColumnModel();
|
||||
col.size = columnSize;
|
||||
this.columns.push(col);
|
||||
}
|
||||
|
||||
if (json.fields) {
|
||||
Object.keys(json.fields).map(key => {
|
||||
let fields = (json.fields[key] || []).map(f => new FormFieldModel(form, f));
|
||||
let col = this.columns[parseInt(key, 10) - 1];
|
||||
col.fields = fields;
|
||||
});
|
||||
}
|
||||
|
||||
this.isExpanded = !this.isCollapsedByDefault();
|
||||
constructor(field: FormFieldModel) {
|
||||
if (field) {
|
||||
super(field.form, field.json);
|
||||
this.field = field;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +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.
|
||||
*/
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -15,10 +15,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface DynamicTableRow {
|
||||
|
||||
isNew: boolean;
|
||||
selected: boolean;
|
||||
value: any;
|
||||
|
||||
export interface FormFieldTemplates {
|
||||
[key: string]: string;
|
||||
}
|
||||
@@ -158,6 +158,9 @@ describe('FormFieldValidator', () => {
|
||||
expect(NumberFieldValidator.isNumber('1')).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber('1.0')).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber('-1')).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber(1)).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber(0)).toBeTruthy();
|
||||
expect(NumberFieldValidator.isNumber(-1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not verify number', () => {
|
||||
@@ -177,6 +180,24 @@ describe('FormFieldValidator', () => {
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow number value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: 44
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow zero number value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
value: 0
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should fail for wrong number value', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.NUMBER,
|
||||
|
||||
@@ -103,11 +103,18 @@ export class NumberFieldValidator implements FormFieldValidator {
|
||||
if (this.isSupported(field)) {
|
||||
if (field.value === null ||
|
||||
field.value === undefined ||
|
||||
field.value === '' ||
|
||||
NumberFieldValidator.isNumber(field.value)) {
|
||||
field.value === '') {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary = 'Input must be a number';
|
||||
let valueStr = '' + field.value;
|
||||
let pattern = new RegExp(/^-?\d+$/);
|
||||
if (field.enableFractions) {
|
||||
pattern = new RegExp(/^-?[0-9]+(\.[0-9]{1,2})?$/);
|
||||
}
|
||||
if (valueStr.match(pattern)) {
|
||||
return true;
|
||||
}
|
||||
field.validationSummary = 'Incorrect number format';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -163,8 +163,8 @@ describe('FormFieldModel', () => {
|
||||
id: 'dropdown-2',
|
||||
type: FormFieldTypes.DROPDOWN,
|
||||
options: [
|
||||
{ id: 'opt1', value: 'Option 1' },
|
||||
{ id: 'opt2', value: 'Option 2' }
|
||||
{ id: 'opt1', name: 'Option 1' },
|
||||
{ id: 'opt2', name: 'Option 2' }
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { FormFieldOption } from './form-field-option';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
import { FormFieldMetadata } from './form-field-metadata';
|
||||
import { FormModel } from './form.model';
|
||||
import { ContainerColumnModel } from './container-column.model';
|
||||
import { WidgetVisibilityModel } from '../../../models/widget-visibility.model';
|
||||
import {
|
||||
FormFieldValidator,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
|
||||
declare var moment: any;
|
||||
|
||||
// Maps to FormFieldRepresentation
|
||||
export class FormFieldModel extends FormWidgetModel {
|
||||
|
||||
private _value: string;
|
||||
@@ -52,6 +54,7 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
overrideId: boolean;
|
||||
tab: string;
|
||||
colspan: number = 1;
|
||||
placeholder: string = null;
|
||||
minLength: number = 0;
|
||||
maxLength: number = 0;
|
||||
minValue: string;
|
||||
@@ -73,7 +76,12 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
enableFractions: boolean = false;
|
||||
currency: string = null;
|
||||
|
||||
// advanced members
|
||||
// container model members
|
||||
numberOfColumns: number = 1;
|
||||
fields: FormFieldModel[] = [];
|
||||
columns: ContainerColumnModel[] = [];
|
||||
|
||||
// util members
|
||||
emptyOption: FormFieldOption;
|
||||
validationSummary: string;
|
||||
validators: FormFieldValidator[] = [];
|
||||
@@ -149,6 +157,34 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
this.enableFractions = <boolean>json.enableFractions;
|
||||
this.currency = json.currency;
|
||||
this._value = this.parseValue(json);
|
||||
|
||||
if (json.placeholder && json.placeholder !== '' && json.placeholder !== 'null') {
|
||||
this.placeholder = json.placeholder;
|
||||
}
|
||||
|
||||
// <container>
|
||||
this.numberOfColumns = <number> json.numberOfColumns;
|
||||
|
||||
let columnSize: number = 12;
|
||||
if (this.numberOfColumns > 1) {
|
||||
columnSize = 12 / this.numberOfColumns;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.numberOfColumns; i++) {
|
||||
let col = new ContainerColumnModel();
|
||||
col.size = columnSize;
|
||||
this.columns.push(col);
|
||||
}
|
||||
|
||||
if (json.fields) {
|
||||
Object.keys(json.fields).map(key => {
|
||||
let fields = (json.fields[key] || []).map(f => new FormFieldModel(form, f));
|
||||
let col = this.columns[parseInt(key, 10) - 1];
|
||||
col.fields = fields;
|
||||
this.fields.push(...fields);
|
||||
});
|
||||
}
|
||||
// </container>
|
||||
}
|
||||
|
||||
if (this.hasEmptyValue && this.options && this.options.length > 0) {
|
||||
|
||||
@@ -19,7 +19,7 @@ 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 { FormValues } from './form-values';
|
||||
import { FormFieldTypes } from './form-field-types';
|
||||
|
||||
describe('FormModel', () => {
|
||||
@@ -197,6 +197,7 @@ describe('FormModel', () => {
|
||||
expect(tab2.fields[0].id).toBe('field2');
|
||||
});
|
||||
|
||||
/*
|
||||
it('should apply external data', () => {
|
||||
let data: FormValues = {
|
||||
field1: 'one',
|
||||
@@ -259,6 +260,7 @@ describe('FormModel', () => {
|
||||
expect(field3.id).toBe('field3');
|
||||
expect(field3.value).toBe('original-value');
|
||||
});
|
||||
*/
|
||||
|
||||
it('should create standard form outcomes', () => {
|
||||
let json = {
|
||||
|
||||
@@ -22,7 +22,7 @@ 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';
|
||||
import { FormFieldTemplates } from './form-field-templates';
|
||||
|
||||
export class FormModel {
|
||||
|
||||
@@ -43,8 +43,10 @@ export class FormModel {
|
||||
|
||||
readOnly: boolean = false;
|
||||
tabs: TabModel[] = [];
|
||||
/** Stores root containers */
|
||||
fields: FormWidgetModel[] = [];
|
||||
outcomes: FormOutcomeModel[] = [];
|
||||
customFieldTemplates: FormFieldTemplates = {};
|
||||
|
||||
values: FormValues = {};
|
||||
|
||||
@@ -73,6 +75,7 @@ export class FormModel {
|
||||
this.taskId = json.taskId;
|
||||
this.taskName = json.taskName || json.name || FormModel.UNSET_TASK_NAME;
|
||||
this.processDefinitionId = json.processDefinitionId;
|
||||
this.customFieldTemplates = json.customFieldTemplates || {};
|
||||
|
||||
let tabCache: FormWidgetModelCache<TabModel> = {};
|
||||
|
||||
@@ -122,14 +125,10 @@ export class FormModel {
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
let field = this.fields[i];
|
||||
|
||||
if (field.type === FormFieldTypes.CONTAINER || field.type === FormFieldTypes.GROUP) {
|
||||
if (field instanceof ContainerModel) {
|
||||
let container = <ContainerModel> field;
|
||||
result.push(...container.getFormFields());
|
||||
}
|
||||
|
||||
if (field.type === FormFieldTypes.DYNAMIC_TABLE) {
|
||||
let dynamicTable = <DynamicTableModel> field;
|
||||
result.push(dynamicTable.field);
|
||||
result.push(container.field);
|
||||
result.push(...container.field.fields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +157,7 @@ export class FormModel {
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
// Activiti supports 2 types of root fields: 'container' and 'dynamic-table'.
|
||||
// Activiti supports 3 types of root fields: container|group|dynamic-table
|
||||
private parseRootFields(json: any): FormWidgetModel[] {
|
||||
let fields = [];
|
||||
|
||||
@@ -171,18 +170,16 @@ export class FormModel {
|
||||
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) {
|
||||
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));
|
||||
result.push(new ContainerModel(new FormFieldModel(this, field)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.push(new ContainerModel(new FormFieldModel(this, field)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,15 +19,13 @@ export * from './form-field-metadata';
|
||||
export * from './form-values';
|
||||
export * from './form-field-types';
|
||||
export * from './form-field-option';
|
||||
export * from './form-field-templates';
|
||||
export * from './form-widget.model';
|
||||
export * from './form-field.model';
|
||||
export * from './form.model';
|
||||
export * from './container-column.model';
|
||||
export * from './container.model';
|
||||
export * from './container-column.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';
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('TabModel', () => {
|
||||
model.fields = [];
|
||||
expect(model.hasContent()).toBeFalsy();
|
||||
|
||||
model.fields = [new ContainerModel(null, null)];
|
||||
model.fields = [new ContainerModel(null)];
|
||||
expect(model.hasContent()).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<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"
|
||||
<div class="mdl-textfield mdl-js-textfield date-widget"
|
||||
[class.date-widget__invalid]="!field.isValid">
|
||||
<label [attr.for]="field.id">{{field.name}}</label>
|
||||
<input class="mdl-textfield__input"
|
||||
type="text"
|
||||
[attr.id]="field.id"
|
||||
@@ -9,8 +10,8 @@
|
||||
[(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>
|
||||
[disabled]="field.readOnly"
|
||||
placeholder="{{field.placeholder}}">
|
||||
<span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { Component, ElementRef, OnInit, AfterViewChecked } from '@angular/core';
|
||||
import { TextFieldWidgetComponent } from './../textfield-widget.component';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -24,14 +24,14 @@ import { TextFieldWidgetComponent } from './../textfield-widget.component';
|
||||
templateUrl: './date.widget.html',
|
||||
styleUrls: ['./date.widget.css']
|
||||
})
|
||||
export class DateWidget extends TextFieldWidgetComponent implements OnInit, AfterViewChecked {
|
||||
export class DateWidget extends WidgetComponent implements OnInit, AfterViewChecked {
|
||||
|
||||
DATE_FORMAT: string = 'D-M-YYYY';
|
||||
|
||||
datePicker: any;
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
super(elementRef);
|
||||
constructor(private elementRef: ElementRef) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -69,7 +69,11 @@ export class DateWidget extends TextFieldWidgetComponent implements OnInit, Afte
|
||||
|
||||
onDateChanged() {
|
||||
if (this.field.value) {
|
||||
this.datePicker.time = moment(this.field.value, this.DATE_FORMAT);
|
||||
let value = moment(this.field.value, this.DATE_FORMAT);
|
||||
if (!value.isValid()) {
|
||||
value = moment();
|
||||
}
|
||||
this.datePicker.time = value;
|
||||
}
|
||||
this.checkVisibility(this.field);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.display-value-widget__dynamic-table .is-disabled {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px dotted rgba(0,0,0,.12);
|
||||
color: rgba(0,0,0,.26);
|
||||
}
|
||||
|
||||
.display-value-widget__dynamic-table table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<div [ngSwitch]="fieldType" class="display-value-widget">
|
||||
<div *ngSwitchCase="'boolean'">
|
||||
<label class="mdl-checkbox mdl-js-checkbox" [attr.for]="field.id">
|
||||
<label class="mdl-checkbox mdl-js-checkbox" [attr.for]="field.id" *ngIf="field?.isVisible">
|
||||
<input type="checkbox"
|
||||
[attr.id]="field.id"
|
||||
[checked]="field.value"
|
||||
[(ngModel)]="value"
|
||||
class="mdl-checkbox__input"
|
||||
disabled>
|
||||
@@ -10,41 +11,46 @@
|
||||
</label>
|
||||
</div>
|
||||
<div *ngSwitchCase="'text'"
|
||||
class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label text-widget">
|
||||
<input class="mdl-textfield__input"
|
||||
class="mdl-textfield mdl-js-textfield text-widget">
|
||||
<label *ngIf="field?.isVisible" [attr.for]="field.id">{{field.name}}</label>
|
||||
<input
|
||||
*ngIf="field?.isVisible"
|
||||
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">
|
||||
class="mdl-textfield mdl-js-textfield multiline-text-widget">
|
||||
<textarea class="mdl-textfield__input"
|
||||
type="text"
|
||||
rows= "3"
|
||||
rows="3"
|
||||
[value]="value"
|
||||
[attr.id]="field.id"
|
||||
disabled>
|
||||
disabled
|
||||
*ngIf="field?.isVisible">
|
||||
</textarea>
|
||||
<label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label>
|
||||
<label *ngIf="field?.isVisible" 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 *ngIf="field?.isVisible">
|
||||
<div>
|
||||
<span>{{field.name}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="'dynamic-table'">
|
||||
<div class="display-value-widget__dynamic-table">
|
||||
<div class="display-value-widget__dynamic-table" *ngIf="field?.isVisible">
|
||||
<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">
|
||||
class="mdl-data-table__cell--non-numeric is-disabled">
|
||||
{{column.name}}
|
||||
</th>
|
||||
</tr>
|
||||
@@ -52,7 +58,7 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let row of rows">
|
||||
<td *ngFor="let column of visibleColumns"
|
||||
class="mdl-data-table__cell--non-numeric">
|
||||
class="mdl-data-table__cell--non-numeric is-disabled">
|
||||
{{ getCellValue(row, column) }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -61,7 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="'upload'">
|
||||
<div class="upload-widget">
|
||||
<div class="upload-widget" *ngIf="field?.isVisible">
|
||||
<label class="upload-widget__label" [attr.for]="field.id">{{field.name}}</label>
|
||||
<div>
|
||||
<i *ngIf="hasFile" class="material-icons upload-widget__icon">attachment</i>
|
||||
@@ -70,14 +76,15 @@
|
||||
</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>
|
||||
class="mdl-textfield mdl-js-textfield text-widget is-disabled is-dirty is-upgraded">
|
||||
<label *ngIf="field?.isVisible" [attr.for]="field.id">{{field.name}}</label>
|
||||
<input
|
||||
*ngIf="field?.isVisible"
|
||||
class="mdl-textfield__input"
|
||||
type="text"
|
||||
[attr.id]="field.id"
|
||||
[value]="value"
|
||||
disabled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,23 +15,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { DisplayValueWidget } from './display-value.widget';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { EcmModelService } from '../../../services/ecm-model.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';
|
||||
import { DynamicTableColumn, DynamicTableRow } from './../dynamic-table/dynamic-table.widget.model';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
describe('DisplayValueWidget', () => {
|
||||
|
||||
let widget: DisplayValueWidget;
|
||||
let formService: FormService;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
|
||||
beforeEach(() => {
|
||||
formService = new FormService(null, null);
|
||||
widget = new DisplayValueWidget(formService);
|
||||
visibilityService = new WidgetVisibilityService(null, null, null);
|
||||
widget = new DisplayValueWidget(formService, visibilityService);
|
||||
});
|
||||
|
||||
it('should require field to setup default value', () => {
|
||||
@@ -670,4 +675,79 @@ describe('DisplayValueWidget', () => {
|
||||
expect(widget.getCellValue(row, column)).toBe(expected);
|
||||
});
|
||||
|
||||
describe('UI check', () => {
|
||||
let widgetUI: DisplayValueWidget;
|
||||
let fixture: ComponentFixture<DisplayValueWidget>;
|
||||
let element: HTMLElement;
|
||||
let componentHandler;
|
||||
|
||||
beforeEach(async(() => {
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
|
||||
window['componentHandler'] = componentHandler;
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [DisplayValueWidget],
|
||||
providers: [
|
||||
EcmModelService,
|
||||
FormService,
|
||||
WidgetVisibilityService
|
||||
]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DisplayValueWidget);
|
||||
widgetUI = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(widgetUI, 'setupMaterialTextField').and.stub();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should show the checkbox on when [BOOLEAN] field is true', async(() => {
|
||||
widgetUI.field = new FormFieldModel(null, {
|
||||
id: 'fake-checkbox-id',
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
value: 'true',
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.BOOLEAN
|
||||
}
|
||||
}
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let elWidget: any = element.querySelector('#fake-checkbox-id');
|
||||
expect(elWidget).toBeDefined();
|
||||
expect(elWidget.checked).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show the checkbox off when [BOOLEAN] field is false', async(() => {
|
||||
widgetUI.field = new FormFieldModel(null, {
|
||||
id: 'fake-checkbox-id',
|
||||
type: FormFieldTypes.DISPLAY_VALUE,
|
||||
value: 'false',
|
||||
params: {
|
||||
field: {
|
||||
type: FormFieldTypes.BOOLEAN
|
||||
}
|
||||
}
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let elWidget: any = element.querySelector('#fake-checkbox-id');
|
||||
expect(elWidget).toBeDefined();
|
||||
expect(elWidget.checked).toBeFalsy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, AfterViewChecked } from '@angular/core';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
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';
|
||||
import { DynamicTableColumn, DynamicTableRow } from './../dynamic-table/dynamic-table.widget.model';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -29,7 +29,7 @@ import { DynamicTableRow } from './../core/dynamic-table-row';
|
||||
templateUrl: './display-value.widget.html',
|
||||
styleUrls: ['./display-value.widget.css']
|
||||
})
|
||||
export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
export class DisplayValueWidget extends WidgetComponent implements OnInit, AfterViewChecked {
|
||||
|
||||
value: any;
|
||||
fieldType: string;
|
||||
@@ -46,14 +46,15 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
// upload/attach
|
||||
hasFile: boolean = false;
|
||||
|
||||
constructor(private formService: FormService) {
|
||||
constructor(private formService: FormService,
|
||||
private visibilityService: WidgetVisibilityService) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
this.value = this.field.value;
|
||||
|
||||
this.visibilityService.refreshEntityVisibility(this.field);
|
||||
if (this.field.params) {
|
||||
let originalField = this.field.params['field'];
|
||||
if (originalField && originalField.type) {
|
||||
@@ -124,7 +125,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
this.visibleColumns = this.columns.filter(col => col.visible);
|
||||
}
|
||||
if (json.value) {
|
||||
this.rows = json.value.map(obj => <DynamicTableRow> {selected: false, value: obj});
|
||||
this.rows = json.value.map(obj => <DynamicTableRow> { selected: false, value: obj });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -136,6 +137,10 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
|
||||
loadRadioButtonValue() {
|
||||
let options = this.field.options || [];
|
||||
let toSelect = options.find(item => item.id === this.field.value);
|
||||
@@ -147,10 +152,10 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
}
|
||||
|
||||
loadRestFieldValue() {
|
||||
if (this.field.form.processDefinitionId) {
|
||||
this.getValuesByProcessDefinitionId();
|
||||
} else {
|
||||
if (this.field.form.taskId) {
|
||||
this.getValuesByTaskId();
|
||||
} else {
|
||||
this.getValuesByProcessDefinitionId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +169,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
(result: FormFieldOption[]) => {
|
||||
let options = result || [];
|
||||
let toSelect = options.find(item => item.id === this.field.value);
|
||||
this.field.options = options;
|
||||
if (toSelect) {
|
||||
this.value = toSelect.name;
|
||||
} else {
|
||||
@@ -184,6 +190,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
|
||||
(result: FormFieldOption[]) => {
|
||||
let options = result || [];
|
||||
let toSelect = options.find(item => item.id === this.field.value);
|
||||
this.field.options = options;
|
||||
if (toSelect) {
|
||||
this.value = toSelect.name;
|
||||
} else {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="dropdown-widget"
|
||||
[class.dropdown-widget__invalid]="!field.isValid">
|
||||
[class.dropdown-widget__invalid]="!field.isValid" *ngIf="field?.isVisible">
|
||||
<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>
|
||||
<option *ngFor="let opt of field.options" [value]="getOptionValue(opt, field.value)" [id]="opt.id">{{opt.name}}</option>
|
||||
</select>
|
||||
<span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span>
|
||||
</div>
|
||||
|
||||
@@ -21,15 +21,21 @@ 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';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { EcmModelService } from '../../../services/ecm-model.service';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
describe('DropdownWidget', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let widget: DropdownWidget;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
|
||||
beforeEach(() => {
|
||||
formService = new FormService(null, null);
|
||||
widget = new DropdownWidget(formService);
|
||||
visibilityService = new WidgetVisibilityService(null, null, null);
|
||||
widget = new DropdownWidget(formService, visibilityService);
|
||||
widget.field = new FormFieldModel(new FormModel());
|
||||
});
|
||||
|
||||
@@ -98,4 +104,161 @@ describe('DropdownWidget', () => {
|
||||
expect(widget.field.options[0]).toBe(emptyOption);
|
||||
expect(widget.field.options[1]).toBe(restFieldValue);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let dropDownWidget: DropdownWidget;
|
||||
let fixture: ComponentFixture<DropdownWidget>;
|
||||
let element: HTMLElement;
|
||||
let componentHandler;
|
||||
let stubFormService;
|
||||
let fakeOptionList: FormFieldOption[] = [{
|
||||
id: 'opt_1',
|
||||
name: 'option_1'
|
||||
}, {
|
||||
id: 'opt_2',
|
||||
name: 'option_2'
|
||||
}, { id: 'opt_3', name: 'option_3' }];
|
||||
|
||||
beforeEach(async(() => {
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
|
||||
window['componentHandler'] = componentHandler;
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [DropdownWidget],
|
||||
providers: [FormService, EcmModelService, WidgetVisibilityService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DropdownWidget);
|
||||
dropDownWidget = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
}));
|
||||
|
||||
describe('and dropdown is populated via taskId', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
visibilityService = fixture.debugElement.injector.get(WidgetVisibilityService);
|
||||
spyOn(visibilityService, 'refreshVisibility').and.stub();
|
||||
spyOn(stubFormService, 'getRestFieldValues').and.returnValue(Observable.of(fakeOptionList));
|
||||
dropDownWidget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
dropDownWidget.field.emptyOption = { id: 'empty', name: 'Choose one...' };
|
||||
dropDownWidget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should show visible dropdown widget', async(() => {
|
||||
expect(element.querySelector('#dropdown-id')).toBeDefined();
|
||||
expect(element.querySelector('#dropdown-id')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_1')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_2')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_3')).not.toBeNull();
|
||||
}));
|
||||
|
||||
it('should select the default value when an option is chosen as default', async(() => {
|
||||
dropDownWidget.field.value = 'option_2';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement).not.toBeNull();
|
||||
expect(element.querySelector('#opt_2')).not.toBeNull();
|
||||
expect(dropDownElement.value).toBe('option_2');
|
||||
expect(dropDownElement.selectedOptions[0].textContent).toBe('option_2');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should select the empty value when no default is chosen', async(() => {
|
||||
dropDownWidget.field.value = 'empty';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement).not.toBeNull();
|
||||
expect(dropDownElement.value).toBe('empty');
|
||||
expect(dropDownElement.selectedOptions[0].textContent).toBe('Choose one...');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be not visibile when isVisible is false', async(() => {
|
||||
dropDownWidget.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should became visibile when isVisible is true', async(() => {
|
||||
dropDownWidget.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#dropdown-id')).toBeNull();
|
||||
dropDownWidget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#dropdown-id')).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via processDefinitionId', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
visibilityService = fixture.debugElement.injector.get(WidgetVisibilityService);
|
||||
spyOn(visibilityService, 'refreshVisibility').and.stub();
|
||||
spyOn(stubFormService, 'getRestFieldValuesByProcessId').and.returnValue(Observable.of(fakeOptionList));
|
||||
dropDownWidget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
dropDownWidget.field.emptyOption = { id: 'empty', name: 'Choose one...' };
|
||||
dropDownWidget.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should show visible dropdown widget', async(() => {
|
||||
expect(element.querySelector('#dropdown-id')).toBeDefined();
|
||||
expect(element.querySelector('#dropdown-id')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_1')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_2')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_3')).not.toBeNull();
|
||||
}));
|
||||
|
||||
it('should select the default value when an option is chosen as default', async(() => {
|
||||
dropDownWidget.field.value = 'option_2';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement).not.toBeNull();
|
||||
expect(element.querySelector('#opt_2')).not.toBeNull();
|
||||
expect(dropDownElement.value).toBe('option_2');
|
||||
expect(dropDownElement.selectedOptions[0].textContent).toBe('option_2');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should select the empty value when no default is chosen', async(() => {
|
||||
dropDownWidget.field.value = 'empty';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
|
||||
expect(dropDownElement).not.toBeNull();
|
||||
expect(dropDownElement.value).toBe('empty');
|
||||
expect(dropDownElement.selectedOptions[0].textContent).toBe('Choose one...');
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
import { FormFieldOption } from './../core/form-field-option';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -28,16 +29,17 @@ import { FormFieldOption } from './../core/form-field-option';
|
||||
})
|
||||
export class DropdownWidget extends WidgetComponent implements OnInit {
|
||||
|
||||
constructor(private formService: FormService) {
|
||||
constructor(private formService: FormService,
|
||||
private visibilityService: WidgetVisibilityService) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field && this.field.restUrl) {
|
||||
if (this.field.form.processDefinitionId) {
|
||||
this.getValuesByProcessDefinitionId();
|
||||
} else {
|
||||
if (this.field.form.taskId) {
|
||||
this.getValuesByTaskId();
|
||||
} else {
|
||||
this.getValuesByProcessDefinitionId();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +82,20 @@ export class DropdownWidget extends WidgetComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
getOptionValue(option: FormFieldOption, fieldValue: string): string {
|
||||
let optionValue: string = '';
|
||||
if (option.id === 'empty' || option.name !== fieldValue) {
|
||||
optionValue = option.id;
|
||||
} else {
|
||||
optionValue = option.name;
|
||||
}
|
||||
return optionValue;
|
||||
}
|
||||
|
||||
checkVisibility() {
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="dynamic-table-widget"
|
||||
[class.dynamic-table-widget__invalid]="!isValid()">
|
||||
[class.dynamic-table-widget__invalid]="!isValid()" *ngIf="field?.isVisible">
|
||||
<div class="dynamic-table-widget__label">{{content.name}}</div>
|
||||
|
||||
<div *ngIf="!editMode">
|
||||
|
||||
@@ -15,11 +15,9 @@
|
||||
* 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';
|
||||
import { FormWidgetModel } from './../core/form-widget.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
|
||||
export class DynamicTableModel extends FormWidgetModel {
|
||||
|
||||
@@ -285,3 +283,41 @@ export class NumberCellValidator implements CellValidator {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
export interface DynamicTableRow {
|
||||
isNew: boolean;
|
||||
selected: boolean;
|
||||
value: any;
|
||||
}
|
||||
@@ -16,16 +16,20 @@
|
||||
*/
|
||||
|
||||
import { DynamicTableWidget } from './dynamic-table.widget';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, FormModel, FormFieldTypes } from './../core/index';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './dynamic-table.widget.model';
|
||||
import { FormModel, FormFieldTypes } from './../core/index';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
describe('DynamicTableWidget', () => {
|
||||
|
||||
let widget: DynamicTableWidget;
|
||||
let table: DynamicTableModel;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
|
||||
beforeEach(() => {
|
||||
table = new DynamicTableModel(null);
|
||||
widget = new DynamicTableWidget(null);
|
||||
visibilityService = new WidgetVisibilityService(null, null, null);
|
||||
widget = new DynamicTableWidget(null, visibilityService);
|
||||
widget.content = table;
|
||||
});
|
||||
|
||||
@@ -91,8 +95,8 @@ describe('DynamicTableWidget', () => {
|
||||
});
|
||||
|
||||
it('should move selection down', () => {
|
||||
let row1 = <DynamicTableRow> { };
|
||||
let row2 = <DynamicTableRow> { };
|
||||
let row1 = <DynamicTableRow> {};
|
||||
let row2 = <DynamicTableRow> {};
|
||||
widget.content.rows.push(...[row1, row2]);
|
||||
widget.content.selectedRow = row1;
|
||||
|
||||
@@ -247,5 +251,4 @@ describe('DynamicTableWidget', () => {
|
||||
let actual = widget.getCellValue(row, column);
|
||||
expect(actual).toBe('GBP 100');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef, OnInit } from '@angular/core';
|
||||
import { WidgetComponent } from './../widget.component';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../core/index';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './dynamic-table.widget.model';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -25,20 +26,27 @@ import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../cor
|
||||
templateUrl: './dynamic-table.widget.html',
|
||||
styleUrls: ['./dynamic-table.widget.css']
|
||||
})
|
||||
export class DynamicTableWidget extends WidgetComponent {
|
||||
export class DynamicTableWidget extends WidgetComponent implements OnInit {
|
||||
|
||||
ERROR_MODEL_NOT_FOUND = 'Table model not found';
|
||||
|
||||
@Input()
|
||||
content: DynamicTableModel;
|
||||
|
||||
editMode: boolean = false;
|
||||
editRow: DynamicTableRow = null;
|
||||
|
||||
constructor(private elementRef: ElementRef) {
|
||||
constructor(private elementRef: ElementRef,
|
||||
private visibilityService: WidgetVisibilityService) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.field) {
|
||||
this.content = new DynamicTableModel(this.field.form, this.field.json);
|
||||
this.visibilityService.refreshVisibility(this.field.form);
|
||||
}
|
||||
}
|
||||
|
||||
isValid() {
|
||||
let result = true;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { BooleanEditorComponent } from './boolean.editor';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../dynamic-table.widget.model';
|
||||
|
||||
describe('BooleanEditorComponent', () => {
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { CellEditorComponent } from './../cell.editor';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { Input } from '@angular/core';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../../core/index';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../dynamic-table.widget.model';
|
||||
|
||||
export abstract class CellEditorComponent {
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import { ElementRef } from '@angular/core';
|
||||
import { DateEditorComponent } from './date.editor';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../../../core/index';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './../../dynamic-table.widget.model';
|
||||
|
||||
describe('DateEditorComponent', () => {
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
<div>
|
||||
<select
|
||||
class="dropdown-editor__select"
|
||||
[attr.id]="column.id"
|
||||
[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>
|
||||
<option *ngFor="let opt of options" [value]="opt.name" [id]="opt.id">{{opt.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,11 +21,13 @@ import {
|
||||
DynamicTableModel,
|
||||
DynamicTableRow,
|
||||
DynamicTableColumn,
|
||||
DynamicTableColumnOption,
|
||||
FormFieldModel,
|
||||
FormModel
|
||||
} from './../../../core/index';
|
||||
DynamicTableColumnOption
|
||||
} from './../../dynamic-table.widget.model';
|
||||
import { FormFieldModel, FormModel } from './../../../core/index';
|
||||
import { FormService } from './../../../../../services/form.service';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { EcmModelService } from '../../../../../services/ecm-model.service';
|
||||
|
||||
describe('DropdownEditorComponent', () => {
|
||||
|
||||
@@ -48,8 +50,8 @@ describe('DropdownEditorComponent', () => {
|
||||
]
|
||||
};
|
||||
|
||||
table = new DynamicTableModel(null);
|
||||
form = new FormModel({ taskId: '<task-id>' });
|
||||
table = new DynamicTableModel(form, null);
|
||||
table.field = new FormFieldModel(form, { id: '<field-id>' });
|
||||
table.rows.push(row);
|
||||
table.columns.push(column);
|
||||
@@ -132,7 +134,7 @@ describe('DropdownEditorComponent', () => {
|
||||
expect(component.value).toBe(row.value[column.id]);
|
||||
});
|
||||
|
||||
it('should handle REST error', () => {
|
||||
it('should handle REST error gettig options with task id', () => {
|
||||
column.optionType = 'rest';
|
||||
const error = 'error';
|
||||
|
||||
@@ -145,10 +147,155 @@ describe('DropdownEditorComponent', () => {
|
||||
expect(component.handleError).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should handle REST error getting option with processDefinitionId', () => {
|
||||
column.optionType = 'rest';
|
||||
let procForm = new FormModel ({processDefinitionId: '<process-definition-id>'});
|
||||
let procTable = new DynamicTableModel(procForm, null);
|
||||
procTable.field = new FormFieldModel(form, { id: '<field-id>' });
|
||||
component.table = procTable;
|
||||
const error = 'error';
|
||||
|
||||
spyOn(formService, 'getRestFieldValuesColumnByProcessId').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]);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
let dropDownEditorComponent: DropdownEditorComponent;
|
||||
let fixture: ComponentFixture<DropdownEditorComponent>;
|
||||
let element: HTMLElement;
|
||||
let componentHandler;
|
||||
let stubFormService;
|
||||
let fakeOptionList: DynamicTableColumnOption[] = [{
|
||||
id: 'opt_1',
|
||||
name: 'option_1'
|
||||
}, {
|
||||
id: 'opt_2',
|
||||
name: 'option_2'
|
||||
}, { id: 'opt_3', name: 'option_3' }];
|
||||
let dynamicTable: DynamicTableModel;
|
||||
|
||||
beforeEach(async(() => {
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
|
||||
window['componentHandler'] = componentHandler;
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [DropdownEditorComponent],
|
||||
providers: [FormService, EcmModelService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DropdownEditorComponent);
|
||||
dropDownEditorComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via taskId', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValuesColumn').and.returnValue(Observable.of(fakeOptionList));
|
||||
row = <DynamicTableRow> { value: { dropdown: 'one' } };
|
||||
column = <DynamicTableColumn> {
|
||||
id: 'column-id',
|
||||
optionType: 'rest',
|
||||
options: [
|
||||
<DynamicTableColumnOption> { id: '1', name: 'one' },
|
||||
<DynamicTableColumnOption> { id: '2', name: 'two' }
|
||||
]
|
||||
};
|
||||
form = new FormModel({ taskId: '<task-id>' });
|
||||
dynamicTable = new DynamicTableModel(form, null);
|
||||
dynamicTable.field = new FormFieldModel(form, { id: '<field-id>' });
|
||||
dynamicTable.rows.push(row);
|
||||
dynamicTable.columns.push(column);
|
||||
dropDownEditorComponent.table = dynamicTable;
|
||||
dropDownEditorComponent.column = column;
|
||||
dropDownEditorComponent.row = row;
|
||||
dropDownEditorComponent.table.field = new FormFieldModel(form, {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
dropDownEditorComponent.table.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should show visible dropdown widget', async(() => {
|
||||
expect(element.querySelector('#column-id')).toBeDefined();
|
||||
expect(element.querySelector('#column-id')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_1')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_2')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_3')).not.toBeNull();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via processDefinitionId', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
stubFormService = fixture.debugElement.injector.get(FormService);
|
||||
spyOn(stubFormService, 'getRestFieldValuesColumnByProcessId').and.returnValue(Observable.of(fakeOptionList));
|
||||
row = <DynamicTableRow> { value: { dropdown: 'one' } };
|
||||
column = <DynamicTableColumn> {
|
||||
id: 'column-id',
|
||||
optionType: 'rest',
|
||||
options: [
|
||||
<DynamicTableColumnOption> { id: '1', name: 'one' },
|
||||
<DynamicTableColumnOption> { id: '2', name: 'two' }
|
||||
]
|
||||
};
|
||||
form = new FormModel({ processDefinitionId: '<proc-id>' });
|
||||
dynamicTable = new DynamicTableModel(form, null);
|
||||
dynamicTable.field = new FormFieldModel(form, { id: '<field-id>' });
|
||||
dynamicTable.rows.push(row);
|
||||
dynamicTable.columns.push(column);
|
||||
dropDownEditorComponent.table = dynamicTable;
|
||||
dropDownEditorComponent.column = column;
|
||||
dropDownEditorComponent.row = row;
|
||||
dropDownEditorComponent.table.field = new FormFieldModel(form, {
|
||||
id: 'dropdown-id',
|
||||
name: 'date-name',
|
||||
type: 'dropdown',
|
||||
readOnly: 'false',
|
||||
restUrl: 'fake-rest-url'
|
||||
});
|
||||
dropDownEditorComponent.table.field.isVisible = true;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should show visible dropdown widget', async(() => {
|
||||
expect(element.querySelector('#column-id')).toBeDefined();
|
||||
expect(element.querySelector('#column-id')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_1')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_2')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_3')).not.toBeNull();
|
||||
}));
|
||||
|
||||
it('should show visible dropdown widget', async(() => {
|
||||
expect(element.querySelector('#column-id')).toBeDefined();
|
||||
expect(element.querySelector('#column-id')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_1')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_2')).not.toBeNull();
|
||||
expect(element.querySelector('#opt_3')).not.toBeNull();
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CellEditorComponent } from './../cell.editor';
|
||||
import { DynamicTableRow, DynamicTableColumn, DynamicTableColumnOption } from './../../../core/index';
|
||||
import { DynamicTableRow, DynamicTableColumn, DynamicTableColumnOption } from './../../dynamic-table.widget.model';
|
||||
import { FormService } from './../../../../../services/form.service';
|
||||
|
||||
@Component({
|
||||
@@ -39,10 +39,10 @@ export class DropdownEditorComponent extends CellEditorComponent implements OnIn
|
||||
let field = this.table.field;
|
||||
if (field) {
|
||||
if (this.column.optionType === 'rest') {
|
||||
if (this.table.form && this.table.form.processDefinitionId) {
|
||||
this.getValuesByProcessDefinitionId(field);
|
||||
} else {
|
||||
if (this.table.form && this.table.form.taskId) {
|
||||
this.getValuesByTaskId(field);
|
||||
} else {
|
||||
this.getValuesByProcessDefinitionId(field);
|
||||
}
|
||||
} else {
|
||||
this.options = this.column.options || [];
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { RowEditorComponent } from './row.editor';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../../core/index';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../dynamic-table.widget.model';
|
||||
|
||||
describe('RowEditorComponent', () => {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../../core/index';
|
||||
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn, DynamicRowValidationSummary } from './../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { TextEditorComponent } from './text.editor';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../dynamic-table.widget.model';
|
||||
|
||||
describe('TextEditorComponent', () => {
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CellEditorComponent } from './../cell.editor';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../../core/index';
|
||||
import { DynamicTableRow, DynamicTableColumn } from './../../dynamic-table.widget.model';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label functional-group-widget"
|
||||
<div class="mdl-textfield mdl-js-textfield functional-group-widget"
|
||||
[class.functional-group-widget__invalid]="!field.isValid">
|
||||
<label [attr.for]="field.id">{{field.name}}</label>
|
||||
<input class="mdl-textfield__input"
|
||||
type="text"
|
||||
[attr.id]="field.id"
|
||||
@@ -7,8 +8,8 @@
|
||||
(ngModelChange)="checkVisibility(field)"
|
||||
(keyup)="onKeyUp($event)"
|
||||
(blur)="onBlur()"
|
||||
[disabled]="field.readOnly">
|
||||
<label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label>
|
||||
[disabled]="field.readOnly"
|
||||
placeholder="{{field.placeholder}}">
|
||||
<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">
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { UnknownWidget } from './unknown/unknown.widget';
|
||||
import { TabsWidget } from './tabs/tabs.widget';
|
||||
import { ContainerWidget } from './container/container.widget';
|
||||
|
||||
@@ -50,6 +51,7 @@ export * from './tabs/tabs.widget';
|
||||
export * from './container/container.widget';
|
||||
|
||||
// primitives
|
||||
export * from './unknown/unknown.widget';
|
||||
export * from './text/text.widget';
|
||||
export * from './number/number.widget';
|
||||
export * from './checkbox/checkbox.widget';
|
||||
@@ -76,6 +78,7 @@ export * from './dynamic-table/editors/boolean/boolean.editor';
|
||||
export * from './dynamic-table/editors/text/text.editor';
|
||||
|
||||
export const WIDGET_DIRECTIVES: any[] = [
|
||||
UnknownWidget,
|
||||
TabsWidget,
|
||||
ContainerWidget,
|
||||
TextWidget,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label multiline-text-widget"
|
||||
<div class="mdl-textfield mdl-js-textfield multiline-text-widget"
|
||||
[class.multiline-text-widget__invalid]="!field.isValid">
|
||||
<label [attr.for]="field.id">{{field.name}}</label>
|
||||
<textarea class="mdl-textfield__input"
|
||||
type="text"
|
||||
rows= "3"
|
||||
@@ -7,9 +8,9 @@
|
||||
[attr.required]="isRequired()"
|
||||
[(ngModel)]="field.value"
|
||||
(ngModelChange)="checkVisibility(field)"
|
||||
[disabled]="field.readOnly">
|
||||
[disabled]="field.readOnly"
|
||||
placeholder="{{field.placeholder}}">
|
||||
</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>
|
||||
|
||||
|
||||