diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts b/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts index 8b05745aca..0fee1a1127 100644 --- a/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts @@ -40,7 +40,7 @@ import { DataSorting } from 'ng2-alfresco-datatable'; 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'; declare var componentHandler; @@ -99,7 +99,8 @@ export class ActivitiDemoComponent implements AfterViewInit { constructor(private elementRef: ElementRef, private route: ActivatedRoute, private apiService: AlfrescoApiService, - private formRenderingService: FormRenderingService) { + private formRenderingService: FormRenderingService, + private formService: FormService) { 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 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() { diff --git a/ng2-components/ng2-activiti-form/README.md b/ng2-components/ng2-activiti-form/README.md index 894a5d102d..4e246cde16 100644 --- a/ng2-components/ng2-activiti-form/README.md +++ b/ng2-components/ng2-activiti-form/README.md @@ -31,6 +31,22 @@

+## Library Contents + +### Components + +- [ActivitiForm](#activitiform-component) +- ActivitiStartForm + +### Services + +- [FormService](#formservice) +- ActivitiAlfrescoContentService +- EcmModelService +- FormRenderingService +- NodeService +- WidgetVisibilityService + ## Prerequisites 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 .config.js) . -## Basic usage examples +## ActivitiForm Component + +### Basic usage The component shows a Form from Activiti @@ -112,6 +130,7 @@ The component shows a Form from Activiti Usage example of this component : **main.ts** + ```ts import { NgModule, Component } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @@ -223,9 +242,9 @@ and store the form field as metadata. The param nameNode is optional. ``` -## Configuration +### Configuration -### Properties +#### Properties 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. | | `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: @@ -255,7 +274,7 @@ The recommended set of properties can be found in the following table: | `form` | FormModel | | Underlying form model instance. | | `debugMode` | boolean | false | Toggle debug mode, allows displaying additional data for development and debugging purposes. | -### Events +#### Events | 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 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 | Create a Form with a fields for each metadata properties | +| createForm | (formName: string) | Observable | Create a Form | +| addFieldsToAForm | (formId: string, formModel: FormDefinitionModel) | Observable | Add Fileds to A form | +| searchFrom | (name: string) | Observable | Search For A Form by name | +| getForms | n/a | Observable | Get All the forms | +| getProcessDefinitions | n/a | Observable | Get Process Definitions | +| getTasks | n/a | Observable | Get All the Tasks | +| getTask | (taskId: string) | Observable | Get Task | +| saveTaskForm | (taskId: string, formValues: FormValues) | Observable | Save Task Form | +| completeTaskForm | (taskId: string, formValues: FormValues, outcome?: string) | Observable | Complete Task Form | +| getTaskForm | (taskId: string) | Observable | Get Form related to a taskId | +| getFormDefinitionById | (formId: string) | Observable | Get Form Definition | +| getFormDefinitionByName | (name: string) | Observable | Returns form definition by a given name. | +| getStartFormInstance | (processId: string) | Observable | Get start form instance for a given processId | +| getStartFormDefinition | (processId: string) | Observable | Get start form definition for a given process | +| createTemporaryRawRelatedContent | (file: any) | Observable | Save File | +| getRestFieldValues | (taskId: string, field: string) | Observable | | +| getRestFieldValuesByProcessId | (processDefinitionId: string, field: string) | Observable | | +| getRestFieldValuesColumnByProcessId | (processDefinitionId: string, field: string, column?: string) | Observable | | +| getRestFieldValuesColumn | (taskId: string, field: string, column?: string) | Observable | | +| getWorkflowGroups\* | (filter: string, groupId?: string) | Observable | | +| getWorkflowUsers\* | (filter: string, groupId?: string) | Observable | | + +\* _Uses private Activiti WebApp api_ + ## See also - [Form Stencils with Angular 2](docs/stencils.md) diff --git a/ng2-components/ng2-activiti-form/index.ts b/ng2-components/ng2-activiti-form/index.ts index 98e9d2970c..110d843dfc 100644 --- a/ng2-components/ng2-activiti-form/index.ts +++ b/ng2-components/ng2-activiti-form/index.ts @@ -37,6 +37,7 @@ 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 * from './src/events/index'; export const ACTIVITI_FORM_DIRECTIVES: any[] = [ ActivitiForm, diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts index 396f30b5d0..45a3837244 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts @@ -15,21 +15,13 @@ * limitations under the License. */ -import { - Component, - OnInit, - AfterViewChecked, - OnChanges, - SimpleChanges, - Input, - Output, - EventEmitter -} from '@angular/core'; +import { Component, OnInit, AfterViewChecked, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core'; import { LogService } from 'ng2-alfresco-core'; import { EcmModelService } from './../services/ecm-model.service'; import { FormService } from './../services/form.service'; import { NodeService } from './../services/node.service'; import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index'; +import { FormEvent, FormErrorEvent } from './../events/index'; 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) { - this.formSaved.emit(this.form); + this.onTaskSaved(this.form); this.storeFormAsMetadata(); return true; } } else { // Note: Activiti is using NAME field rather than ID for outcomes if (outcome.name) { - this.formSaved.emit(this.form); + this.onTaskSaved(this.form); this.completeTaskForm(outcome.name); return true; } @@ -345,8 +337,8 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { .getTaskForm(taskId) .subscribe( form => { - this.form = new FormModel(form, data, this.readOnly); - this.formLoaded.emit(this.form); + this.form = new FormModel(form, data, this.readOnly, this.formService); + this.onFormLoaded(this.form); }, (error) => { this.handleError(error); @@ -361,7 +353,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { form => { this.formName = form.name; this.form = this.parseForm(form); - this.formLoaded.emit(this.form); + this.onFormLoaded(this.form); }, (error) => { this.handleError(error); @@ -377,7 +369,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.formService.getFormDefinitionById(id).subscribe( form => { this.form = this.parseForm(form); - this.formLoaded.emit(this.form); + this.onFormLoaded(this.form); }, (error) => { this.handleError(error); @@ -396,12 +388,10 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { .saveTaskForm(this.form.taskId, this.form.values) .subscribe( () => { - this.formSaved.emit(this.form); + this.onTaskSaved(this.form); this.storeFormAsMetadata(); }, - (error) => { - this.handleError(error); - } + error => this.onTaskSavedError(this.form, error) ); } } @@ -412,12 +402,10 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { .completeTaskForm(this.form.taskId, this.form.values, outcome) .subscribe( () => { - this.formCompleted.emit(this.form); + this.onTaskCompleted(this.form); this.storeFormAsMetadata(); }, - (error) => { - this.handleError(error); - } + error => this.onTaskCompletedError(this.form, error) ); } } @@ -429,7 +417,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { parseForm(json: any): FormModel { 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) { 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)); + } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts index cc31cf1101..8741224b54 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts @@ -23,6 +23,8 @@ import { FormOutcomeModel } from './form-outcome.model'; import { FormFieldModel } from './form-field.model'; import { FormFieldTypes } from './form-field-types'; import { FormFieldTemplates } from './form-field-templates'; +import { FormService } from './../../../services/form.service'; +import { FormFieldEvent } from './../../../events/index'; export class FormModel { @@ -66,7 +68,7 @@ export class FormModel { 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; if (json) { @@ -120,6 +122,9 @@ export class FormModel { onFormFieldChanged(field: FormFieldModel) { this.validateField(field); + if (this.formService) { + this.formService.formFieldValueChanged.next(new FormFieldEvent(this, field)); + } } // TODO: consider evaluating and caching once the form is loaded diff --git a/ng2-components/ng2-activiti-form/src/events/form-error.event.ts b/ng2-components/ng2-activiti-form/src/events/form-error.event.ts new file mode 100644 index 0000000000..1e4ad50c31 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/events/form-error.event.ts @@ -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; + } + +} diff --git a/ng2-components/ng2-activiti-form/src/events/form-field.event.ts b/ng2-components/ng2-activiti-form/src/events/form-field.event.ts new file mode 100644 index 0000000000..1189e33a64 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/events/form-field.event.ts @@ -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; + } + +} diff --git a/ng2-components/ng2-activiti-form/src/events/form.event.ts b/ng2-components/ng2-activiti-form/src/events/form.event.ts new file mode 100644 index 0000000000..841a12dd66 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/events/form.event.ts @@ -0,0 +1,27 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FormModel } from './../components/widgets/core/index'; + +export class FormEvent { + + readonly form: FormModel; + + constructor(form: FormModel) { + this.form = form; + } +} diff --git a/ng2-components/ng2-activiti-form/src/events/index.ts b/ng2-components/ng2-activiti-form/src/events/index.ts new file mode 100644 index 0000000000..39a1671687 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/events/index.ts @@ -0,0 +1,20 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './form.event'; +export * from './form-error.event'; +export * from './form-field.event'; diff --git a/ng2-components/ng2-activiti-form/src/services/form.service.ts b/ng2-components/ng2-activiti-form/src/services/form.service.ts index 85f77dc42a..0ffc0156d3 100644 --- a/ng2-components/ng2-activiti-form/src/services/form.service.ts +++ b/ng2-components/ng2-activiti-form/src/services/form.service.ts @@ -16,13 +16,14 @@ */ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Rx'; +import { Observable, Subject } from 'rxjs/Rx'; import { AlfrescoApiService, LogService } from 'ng2-alfresco-core'; import { FormValues } from './../components/widgets/core/index'; import { FormDefinitionModel } from '../models/form-definition.model'; import { EcmModelService } from './ecm-model.service'; import { GroupModel } from './../components/widgets/core/group.model'; import { GroupUserModel } from './../components/widgets/core/group-user.model'; +import { FormEvent, FormErrorEvent, FormFieldEvent } from './../events/index'; @Injectable() export class FormService { @@ -30,6 +31,13 @@ export class FormService { static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error'; static GENERIC_ERROR_MESSAGE: string = 'Server error'; + formLoaded: Subject = new Subject(); + formFieldValueChanged: Subject = new Subject(); + taskCompleted: Subject = new Subject(); + taskCompletedError: Subject = new Subject(); + taskSaved: Subject = new Subject(); + taskSavedError: Subject = new Subject(); + constructor(private ecmModelService: EcmModelService, private apiService: AlfrescoApiService, private logService: LogService) { @@ -111,7 +119,7 @@ export class FormService { } /** - * Get Process Definition + * Get Process Definitions * @returns {Observable} */ getProcessDefinitions(): Observable { @@ -122,7 +130,6 @@ export class FormService { /** * Get All the Tasks - * @param taskId Task Id * @returns {Observable} */ getTasks(): Observable { @@ -196,7 +203,7 @@ export class FormService { } /** - * Returns form definition ID by a given name. + * Returns form definition by a given name. * @param name * @returns {Promise|Promise} */ @@ -328,7 +335,7 @@ export class FormService { }); } - getFormId(res: any) { + getFormId(res: any): string { let result = null; if (res && res.data && res.data.length > 0) {