basic form events hooks and readme updates (#1528)

* basic form events hooks

FormService now exposes the following events:

- formLoaded (formRendered alternative)
- formFieldValueChanged
- taskCompleted
- taskCompletedError
- taskSaved
- taskSavedError

* readme updates
This commit is contained in:
Denys Vuika
2017-01-25 10:44:41 +00:00
committed by Mario Romano
parent dd753ba890
commit bd485c730f
10 changed files with 263 additions and 38 deletions

View File

@@ -40,7 +40,7 @@ import {
DataSorting DataSorting
} from 'ng2-alfresco-datatable'; } from 'ng2-alfresco-datatable';
import { AlfrescoApiService } from 'ng2-alfresco-core'; import { AlfrescoApiService } from 'ng2-alfresco-core';
import { FormRenderingService } from 'ng2-activiti-form'; import { FormService, FormRenderingService, FormEvent, FormFieldEvent } from 'ng2-activiti-form';
import { /*CustomEditorComponent*/ CustomStencil01 } from './custom-editor/custom-editor.component'; import { /*CustomEditorComponent*/ CustomStencil01 } from './custom-editor/custom-editor.component';
declare var componentHandler; declare var componentHandler;
@@ -99,7 +99,8 @@ export class ActivitiDemoComponent implements AfterViewInit {
constructor(private elementRef: ElementRef, constructor(private elementRef: ElementRef,
private route: ActivatedRoute, private route: ActivatedRoute,
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private formRenderingService: FormRenderingService) { private formRenderingService: FormRenderingService,
private formService: FormService) {
this.dataTasks = new ObjectDataTableAdapter( this.dataTasks = new ObjectDataTableAdapter(
[], [],
[ [
@@ -123,6 +124,14 @@ export class ActivitiDemoComponent implements AfterViewInit {
// Uncomment this line to map 'custom_stencil_01' to local editor component // Uncomment this line to map 'custom_stencil_01' to local editor component
formRenderingService.setComponentTypeResolver('custom_stencil_01', () => CustomStencil01, true); formRenderingService.setComponentTypeResolver('custom_stencil_01', () => CustomStencil01, true);
formService.formLoaded.subscribe((e: FormEvent) => {
console.log(`Form loaded: ${e.form.id}`);
});
formService.formFieldValueChanged.subscribe((e: FormFieldEvent) => {
console.log(`Field value changed. Form: ${e.form.id}, Field: ${e.field.id}, Value: ${e.field.value}`);
});
} }
ngOnInit() { ngOnInit() {

View File

@@ -31,6 +31,22 @@
</a> </a>
</p> </p>
## Library Contents
### Components
- [ActivitiForm](#activitiform-component)
- ActivitiStartForm
### Services
- [FormService](#formservice)
- ActivitiAlfrescoContentService
- EcmModelService
- FormRenderingService
- NodeService
- WidgetVisibilityService
## Prerequisites ## Prerequisites
Before you start using this development framework, make sure you have installed all required software and done all the Before you start using this development framework, make sure you have installed all required software and done all the
@@ -101,7 +117,9 @@ Follow the 3 steps below:
Please refer to the following example file: [systemjs.config.js](demo/systemjs Please refer to the following example file: [systemjs.config.js](demo/systemjs
.config.js) . .config.js) .
## Basic usage examples ## ActivitiForm Component
### Basic usage
The component shows a Form from Activiti The component shows a Form from Activiti
@@ -112,6 +130,7 @@ The component shows a Form from Activiti
Usage example of this component : Usage example of this component :
**main.ts** **main.ts**
```ts ```ts
import { NgModule, Component } from '@angular/core'; import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
@@ -223,9 +242,9 @@ and store the form field as metadata. The param nameNode is optional.
</activiti-form> </activiti-form>
``` ```
## Configuration ### Configuration
### Properties #### Properties
The recommended set of properties can be found in the following table: The recommended set of properties can be found in the following table:
@@ -246,7 +265,7 @@ The recommended set of properties can be found in the following table:
| `path` | string | | Path of the folder where to store the metadata. | | `path` | string | | Path of the folder where to store the metadata. |
| `nameNode` (optional) | string | true | Name to assign to the new node where the metadata are stored. | | `nameNode` (optional) | string | true | Name to assign to the new node where the metadata are stored. |
#### Advanced properties ##### Advanced properties
The following properties are for complex customisation purposes: The following properties are for complex customisation purposes:
@@ -255,7 +274,7 @@ The recommended set of properties can be found in the following table:
| `form` | FormModel | | Underlying form model instance. | | `form` | FormModel | | Underlying form model instance. |
| `debugMode` | boolean | false | Toggle debug mode, allows displaying additional data for development and debugging purposes. | | `debugMode` | boolean | false | Toggle debug mode, allows displaying additional data for development and debugging purposes. |
### Events #### Events
| Name | Description | | Name | Description |
| --- | --- | | --- | --- |
@@ -332,6 +351,70 @@ 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 **Please note that if `event.preventDefault()` is not called then default outcome behaviour
will also be executed after your custom code.** will also be executed after your custom code.**
## FormService
```ts
import { Component } from '@angular/core';
import { FormService, FormEvent, FormFieldEvent } from 'ng2-activiti-form';
@Component(...)
class MyComponent {
constructor(private formService: FormService) {
formService.formLoaded.subscribe((e: FormEvent) => {
console.log(`Form loaded: ${e.form.id}`);
});
formService.formFieldValueChanged.subscribe((e: FormFieldEvent) => {
console.log(`Field value changed. Form: ${e.form.id}, Field: ${e.field.id}, Value: ${e.field.value}`);
});
}
}
```
### Events
| Name | Args Type | Description |
| --- | --- | --- |
| formLoaded | FormEvent | Raised when form has been loaded or reloaded |
| formFieldValueChanged | FormFieldEvent | Raised when input values change |
| taskCompleted | FormEvent | Raised when a task is completed successfully |
| taskCompletedError | FormErrorEvent | Raised when a task is completed unsuccessfully |
| taskSaved | FormEvent | Raised when a task is saved successfully |
| taskSavedError | FormErrorEvent | Raised when a task is saved unsuccessfully |
### Methods
| Name | Params | Returns | Description |
| --- | --- | --- | --- |
| createFormFromANode | (formName: string) | Observable<any\> | Create a Form with a fields for each metadata properties |
| createForm | (formName: string) | Observable<any\> | Create a Form |
| addFieldsToAForm | (formId: string, formModel: FormDefinitionModel) | Observable<any\> | Add Fileds to A form |
| searchFrom | (name: string) | Observable<any\> | Search For A Form by name |
| getForms | n/a | Observable<any\> | Get All the forms |
| getProcessDefinitions | n/a | Observable<any\> | Get Process Definitions |
| getTasks | n/a | Observable<any\> | Get All the Tasks |
| getTask | (taskId: string) | Observable<any\> | Get Task |
| saveTaskForm | (taskId: string, formValues: FormValues) | Observable<any\> | Save Task Form |
| completeTaskForm | (taskId: string, formValues: FormValues, outcome?: string) | Observable<any\> | Complete Task Form |
| getTaskForm | (taskId: string) | Observable<any\> | Get Form related to a taskId |
| getFormDefinitionById | (formId: string) | Observable<any\> | Get Form Definition |
| getFormDefinitionByName | (name: string) | Observable<any\> | Returns form definition by a given name. |
| getStartFormInstance | (processId: string) | Observable<any\> | Get start form instance for a given processId |
| getStartFormDefinition | (processId: string) | Observable<any\> | Get start form definition for a given process |
| createTemporaryRawRelatedContent | (file: any) | Observable<any\> | Save File |
| getRestFieldValues | (taskId: string, field: string) | Observable<any\> | |
| getRestFieldValuesByProcessId | (processDefinitionId: string, field: string) | Observable<any\> | |
| getRestFieldValuesColumnByProcessId | (processDefinitionId: string, field: string, column?: string) | Observable<any\> | |
| getRestFieldValuesColumn | (taskId: string, field: string, column?: string) | Observable<any\> | |
| getWorkflowGroups\* | (filter: string, groupId?: string) | Observable<GroupModel[]\> | |
| getWorkflowUsers\* | (filter: string, groupId?: string) | Observable<GroupUserModel[]\> | |
\* _Uses private Activiti WebApp api_
## See also ## See also
- [Form Stencils with Angular 2](docs/stencils.md) - [Form Stencils with Angular 2](docs/stencils.md)

View File

@@ -37,6 +37,7 @@ export * from './src/components/widgets/index';
export * from './src/services/ecm-model.service'; export * from './src/services/ecm-model.service';
export * from './src/services/node.service'; export * from './src/services/node.service';
export * from './src/services/form-rendering.service'; export * from './src/services/form-rendering.service';
export * from './src/events/index';
export const ACTIVITI_FORM_DIRECTIVES: any[] = [ export const ACTIVITI_FORM_DIRECTIVES: any[] = [
ActivitiForm, ActivitiForm,

View File

@@ -15,21 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { import { Component, OnInit, AfterViewChecked, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core';
Component,
OnInit,
AfterViewChecked,
OnChanges,
SimpleChanges,
Input,
Output,
EventEmitter
} from '@angular/core';
import { LogService } from 'ng2-alfresco-core'; import { LogService } from 'ng2-alfresco-core';
import { EcmModelService } from './../services/ecm-model.service'; import { EcmModelService } from './../services/ecm-model.service';
import { FormService } from './../services/form.service'; import { FormService } from './../services/form.service';
import { NodeService } from './../services/node.service'; import { NodeService } from './../services/node.service';
import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index'; import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index';
import { FormEvent, FormErrorEvent } from './../events/index';
import { WidgetVisibilityService } from './../services/widget-visibility.service'; import { WidgetVisibilityService } from './../services/widget-visibility.service';
@@ -272,14 +264,14 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
} }
if (outcome.id === ActivitiForm.CUSTOM_OUTCOME_ID) { if (outcome.id === ActivitiForm.CUSTOM_OUTCOME_ID) {
this.formSaved.emit(this.form); this.onTaskSaved(this.form);
this.storeFormAsMetadata(); this.storeFormAsMetadata();
return true; return true;
} }
} else { } else {
// Note: Activiti is using NAME field rather than ID for outcomes // Note: Activiti is using NAME field rather than ID for outcomes
if (outcome.name) { if (outcome.name) {
this.formSaved.emit(this.form); this.onTaskSaved(this.form);
this.completeTaskForm(outcome.name); this.completeTaskForm(outcome.name);
return true; return true;
} }
@@ -345,8 +337,8 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
.getTaskForm(taskId) .getTaskForm(taskId)
.subscribe( .subscribe(
form => { form => {
this.form = new FormModel(form, data, this.readOnly); this.form = new FormModel(form, data, this.readOnly, this.formService);
this.formLoaded.emit(this.form); this.onFormLoaded(this.form);
}, },
(error) => { (error) => {
this.handleError(error); this.handleError(error);
@@ -361,7 +353,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
form => { form => {
this.formName = form.name; this.formName = form.name;
this.form = this.parseForm(form); this.form = this.parseForm(form);
this.formLoaded.emit(this.form); this.onFormLoaded(this.form);
}, },
(error) => { (error) => {
this.handleError(error); this.handleError(error);
@@ -377,7 +369,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
this.formService.getFormDefinitionById(id).subscribe( this.formService.getFormDefinitionById(id).subscribe(
form => { form => {
this.form = this.parseForm(form); this.form = this.parseForm(form);
this.formLoaded.emit(this.form); this.onFormLoaded(this.form);
}, },
(error) => { (error) => {
this.handleError(error); this.handleError(error);
@@ -396,12 +388,10 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
.saveTaskForm(this.form.taskId, this.form.values) .saveTaskForm(this.form.taskId, this.form.values)
.subscribe( .subscribe(
() => { () => {
this.formSaved.emit(this.form); this.onTaskSaved(this.form);
this.storeFormAsMetadata(); this.storeFormAsMetadata();
}, },
(error) => { error => this.onTaskSavedError(this.form, error)
this.handleError(error);
}
); );
} }
} }
@@ -412,12 +402,10 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
.completeTaskForm(this.form.taskId, this.form.values, outcome) .completeTaskForm(this.form.taskId, this.form.values, outcome)
.subscribe( .subscribe(
() => { () => {
this.formCompleted.emit(this.form); this.onTaskCompleted(this.form);
this.storeFormAsMetadata(); this.storeFormAsMetadata();
}, },
(error) => { error => this.onTaskCompletedError(this.form, error)
this.handleError(error);
}
); );
} }
} }
@@ -429,7 +417,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
parseForm(json: any): FormModel { parseForm(json: any): FormModel {
if (json) { if (json) {
let form = new FormModel(json, this.data, this.readOnly); let form = new FormModel(json, this.data, this.readOnly, this.formService);
if (!json.fields) { if (!json.fields) {
form.outcomes = this.getFormDefinitionOutcomes(form); form.outcomes = this.getFormDefinitionOutcomes(form);
} }
@@ -496,4 +484,29 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
); );
} }
} }
protected onFormLoaded(form: FormModel) {
this.formLoaded.emit(form);
this.formService.formLoaded.next(new FormEvent(form));
}
protected onTaskSaved(form: FormModel) {
this.formSaved.emit(form);
this.formService.taskSaved.next(new FormEvent(form));
}
protected onTaskSavedError(form: FormModel, error: any) {
this.handleError(error);
this.formService.taskSavedError.next(new FormErrorEvent(form, error));
}
protected onTaskCompleted(form: FormModel) {
this.formCompleted.emit(form);
this.formService.taskCompleted.next(new FormEvent(form));
}
protected onTaskCompletedError(form: FormModel, error: any) {
this.handleError(error);
this.formService.taskCompletedError.next(new FormErrorEvent(form, error));
}
} }

View File

@@ -23,6 +23,8 @@ import { FormOutcomeModel } from './form-outcome.model';
import { FormFieldModel } from './form-field.model'; import { FormFieldModel } from './form-field.model';
import { FormFieldTypes } from './form-field-types'; import { FormFieldTypes } from './form-field-types';
import { FormFieldTemplates } from './form-field-templates'; import { FormFieldTemplates } from './form-field-templates';
import { FormService } from './../../../services/form.service';
import { FormFieldEvent } from './../../../events/index';
export class FormModel { export class FormModel {
@@ -66,7 +68,7 @@ export class FormModel {
return this.outcomes && this.outcomes.length > 0; return this.outcomes && this.outcomes.length > 0;
} }
constructor(json?: any, data?: FormValues, readOnly: boolean = false) { constructor(json?: any, data?: FormValues, readOnly: boolean = false, protected formService?: FormService) {
this.readOnly = readOnly; this.readOnly = readOnly;
if (json) { if (json) {
@@ -120,6 +122,9 @@ export class FormModel {
onFormFieldChanged(field: FormFieldModel) { onFormFieldChanged(field: FormFieldModel) {
this.validateField(field); this.validateField(field);
if (this.formService) {
this.formService.formFieldValueChanged.next(new FormFieldEvent(this, field));
}
} }
// TODO: consider evaluating and caching once the form is loaded // TODO: consider evaluating and caching once the form is loaded

View File

@@ -0,0 +1,30 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormEvent } from './form.event';
import { FormModel } from './../components/widgets/core/index';
export class FormErrorEvent extends FormEvent {
readonly error: any;
constructor(form: FormModel, error: any) {
super(form);
this.error = error;
}
}

View File

@@ -0,0 +1,30 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormEvent } from './form.event';
import { FormModel, FormFieldModel } from './../components/widgets/core/index';
export class FormFieldEvent extends FormEvent {
readonly field: FormFieldModel;
constructor(form: FormModel, field: FormFieldModel) {
super(form);
this.field = field;
}
}

View File

@@ -0,0 +1,27 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormModel } from './../components/widgets/core/index';
export class FormEvent {
readonly form: FormModel;
constructor(form: FormModel) {
this.form = form;
}
}

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './form.event';
export * from './form-error.event';
export * from './form-field.event';

View File

@@ -16,13 +16,14 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx'; import { Observable, Subject } from 'rxjs/Rx';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core'; import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { FormValues } from './../components/widgets/core/index'; import { FormValues } from './../components/widgets/core/index';
import { FormDefinitionModel } from '../models/form-definition.model'; import { FormDefinitionModel } from '../models/form-definition.model';
import { EcmModelService } from './ecm-model.service'; import { EcmModelService } from './ecm-model.service';
import { GroupModel } from './../components/widgets/core/group.model'; import { GroupModel } from './../components/widgets/core/group.model';
import { GroupUserModel } from './../components/widgets/core/group-user.model'; import { GroupUserModel } from './../components/widgets/core/group-user.model';
import { FormEvent, FormErrorEvent, FormFieldEvent } from './../events/index';
@Injectable() @Injectable()
export class FormService { export class FormService {
@@ -30,6 +31,13 @@ export class FormService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error'; static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error'; static GENERIC_ERROR_MESSAGE: string = 'Server error';
formLoaded: Subject<FormEvent> = new Subject<FormEvent>();
formFieldValueChanged: Subject<FormFieldEvent> = new Subject<FormFieldEvent>();
taskCompleted: Subject<FormEvent> = new Subject<FormEvent>();
taskCompletedError: Subject<FormErrorEvent> = new Subject<FormErrorEvent>();
taskSaved: Subject<FormEvent> = new Subject<FormEvent>();
taskSavedError: Subject<FormErrorEvent> = new Subject<FormErrorEvent>();
constructor(private ecmModelService: EcmModelService, constructor(private ecmModelService: EcmModelService,
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private logService: LogService) { private logService: LogService) {
@@ -111,7 +119,7 @@ export class FormService {
} }
/** /**
* Get Process Definition * Get Process Definitions
* @returns {Observable<any>} * @returns {Observable<any>}
*/ */
getProcessDefinitions(): Observable<any> { getProcessDefinitions(): Observable<any> {
@@ -122,7 +130,6 @@ export class FormService {
/** /**
* Get All the Tasks * Get All the Tasks
* @param taskId Task Id
* @returns {Observable<any>} * @returns {Observable<any>}
*/ */
getTasks(): Observable<any> { getTasks(): Observable<any> {
@@ -196,7 +203,7 @@ export class FormService {
} }
/** /**
* Returns form definition ID by a given name. * Returns form definition by a given name.
* @param name * @param name
* @returns {Promise<T>|Promise<ErrorObservable>} * @returns {Promise<T>|Promise<ErrorObservable>}
*/ */
@@ -328,7 +335,7 @@ export class FormService {
}); });
} }
getFormId(res: any) { getFormId(res: any): string {
let result = null; let result = null;
if (res && res.data && res.data.length > 0) { if (res && res.data && res.data.length > 0) {