From 514fbffea989909b7143ef63c0887faf991031d0 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Wed, 7 Sep 2016 10:44:22 +0100 Subject: [PATCH] #636 functional-group (aka group picker) widget --- .../widgets/core/group-user.model.ts | 26 ++++++ .../components/widgets/core/group.model.ts | 26 ++++++ .../functional-group.widget.css | 26 ++++++ .../functional-group.widget.html | 12 +++ .../functional-group.widget.ts | 55 +++++++++++- .../widgets/typeahead/typeahead.widget.ts | 1 + .../src/services/form.service.ts | 89 +++++++++++++------ 7 files changed, 205 insertions(+), 30 deletions(-) create mode 100644 ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts create mode 100644 ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts new file mode 100644 index 0000000000..dd3769c367 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts @@ -0,0 +1,26 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class GroupUserModel { + + company: string; + email: string; + firstName: string; + id: string; + lastName: string; + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts new file mode 100644 index 0000000000..de701aec0a --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts @@ -0,0 +1,26 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class GroupModel { + + externalId: string; + groups: any; + id: string; + name: string; + status: string; + +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css index 95e76bb511..6533c16189 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css @@ -1,3 +1,29 @@ .functional-group-widget { width: 100%; } + +.functional-group-widget--autocomplete { + background-color: #fff; + position: absolute; + z-index: 5; + color: #555; + margin: -15px 0 0 0; +} + +.functional-group-widget--autocomplete > ul { + list-style-type: none; + position: static; + + height: auto; + width: auto; + min-width: 124px; + padding: 8px 0; + margin: 0; + + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); + border-radius: 2px; +} + +.functional-group-widget--autocomplete > ul > li { + opacity: 1; +} diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html index 6939b86bd8..7a9bf8f2a5 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html @@ -4,6 +4,18 @@ [attr.id]="field.id" [(ngModel)]="value" (ngModelChange)="checkVisibility(field)" + (keyup)="onKeyUp($event)" + (blur)="onBlur()" [disabled]="field.readOnly"> + +
+ +
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts index 9988a6c0d4..37a40337b0 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts @@ -17,6 +17,8 @@ import { Component, OnInit } from '@angular/core'; import { WidgetComponent } from './../widget.component'; +import { FormService } from '../../../services/form.service'; +import { GroupModel } from './../core/group.model'; declare let __moduleName: string; declare var componentHandler; @@ -30,15 +32,66 @@ declare var componentHandler; export class FunctionalGroupWidget extends WidgetComponent implements OnInit { value: string; + popupVisible: boolean = false; + groups: GroupModel[] = []; + minTermLength: number = 1; - constructor() { + constructor(private formService: FormService) { super(); } + // TODO: investigate, called 2 times + // https://github.com/angular/angular/issues/6782 ngOnInit() { let group = this.field.value; if (group) { this.value = group.name; } } + + onKeyUp(event: KeyboardEvent) { + if (this.value && this.value.length >= this.minTermLength) { + this.formService.getWorkflowGroups(this.value) + .subscribe((result: GroupModel[]) => { + this.groups = result || []; + this.popupVisible = this.groups.length > 0; + }); + } else { + this.popupVisible = false; + } + + } + + onBlur() { + setTimeout(() => { + this.flushValue(); + }, 200); + } + + flushValue() { + this.popupVisible = false; + + let option = this.groups.find(item => item.name.toLocaleLowerCase() === this.value.toLocaleLowerCase()); + + if (option) { + this.field.value = option; + this.value = option.name; + } else { + this.field.value = null; + this.value = null; + } + + this.field.updateForm(); + } + + // TODO: still causes onBlur execution + onItemClick(item: GroupModel, event: Event) { + if (item) { + this.field.value = item; + this.value = item.name; + } + if (event) { + event.preventDefault(); + } + } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.ts index 2eaeae6db2..8ed39cc2ef 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.ts @@ -97,6 +97,7 @@ export class TypeaheadWidget extends WidgetComponent implements OnInit { this.field.updateForm(); } + // TODO: still causes onBlur execution onItemClick(item: FormFieldOption, event: Event) { if (item) { this.field.value = item.id; 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 cd26ce0ca2..34040c5b1b 100644 --- a/ng2-components/ng2-activiti-form/src/services/form.service.ts +++ b/ng2-components/ng2-activiti-form/src/services/form.service.ts @@ -17,10 +17,11 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Rx'; -import { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { AlfrescoApiService } from 'ng2-alfresco-core'; import { FormValues } from './../components/widgets/core/index'; import { FormDefinitionModel } from '../models/form-definition.model'; import { EcmModelService } from './ecm-model.service'; +import { GroupModel } from './../components/widgets/core/group.model'; @Injectable() export class FormService { @@ -28,15 +29,15 @@ export class FormService { static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error'; static GENERIC_ERROR_MESSAGE: string = 'Server error'; - constructor(private authService: AlfrescoAuthenticationService, - private ecmModelService: EcmModelService) { + constructor(private ecmModelService: EcmModelService, + private apiService: AlfrescoApiService) { } /** * Create a Form with a fields for each metadata properties * @returns {Observable} */ - public createFormFromANode(formName: string): Observable { + createFormFromANode(formName: string): Observable { return Observable.create(observer => { this.createForm(formName).subscribe( form => { @@ -57,7 +58,7 @@ export class FormService { * Create a Form * @returns {Observable} */ - public createForm(formName: string): Observable { + createForm(formName: string): Observable { let dataModel = { name: formName, description: '', @@ -65,27 +66,27 @@ export class FormService { stencilSet: 0 }; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.createModel(dataModel)); + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.createModel(dataModel)); } /** * Add Fileds to A form * @returns {Observable} */ - public addFieldsToAForm(formId: string, formModel: FormDefinitionModel): Observable { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.editorApi.saveForm(formId, formModel)); + addFieldsToAForm(formId: string, formModel: FormDefinitionModel): Observable { + return Observable.fromPromise(this.apiService.getInstance().activiti.editorApi.saveForm(formId, formModel)); } /** * Search For A Form by name * @returns {Observable} */ - public searchFrom(name: string): Observable { + searchFrom(name: string): Observable { let opts = { 'modelType': 2 }; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)).map(function (forms: any) { + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.getModels(opts)).map(function (forms: any) { return forms.data.find(formdata => formdata.name === name); }).catch(this.handleError); } @@ -94,20 +95,20 @@ export class FormService { * Get All the forms * @returns {Observable} */ - public getForms(): Observable { + getForms(): Observable { let opts = { 'modelType': 2 }; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)); + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.getModels(opts)); } /** * Get Process Definition * @returns {Observable} */ - public getProcessDefinitions(): Observable { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessDefinitions({})) + getProcessDefinitions(): Observable { + return Observable.fromPromise(this.apiService.getInstance().activiti.processApi.getProcessDefinitions({})) .map(this.toJsonArray) .catch(this.handleError); } @@ -117,8 +118,8 @@ export class FormService { * @param taskId Task Id * @returns {Observable} */ - public getTasks(): Observable { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.listTasks({})) + getTasks(): Observable { + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.listTasks({})) .map(this.toJsonArray) .catch(this.handleError); } @@ -128,8 +129,8 @@ export class FormService { * @param taskId Task Id * @returns {Observable} */ - public getTask(taskId: string): Observable { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTask(taskId)) + getTask(taskId: string): Observable { + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTask(taskId)) .map(this.toJson) .catch(this.handleError); } @@ -140,10 +141,10 @@ export class FormService { * @param formValues Form Values * @returns {Observable} */ - public saveTaskForm(taskId: string, formValues: FormValues): Observable { + saveTaskForm(taskId: string, formValues: FormValues): Observable { let body = JSON.stringify({values: formValues}); - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.saveTaskForm(taskId, body)) + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.saveTaskForm(taskId, body)) .catch(this.handleError); } @@ -154,14 +155,14 @@ export class FormService { * @param outcome Form Outcome * @returns {Observable} */ - public completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable { + completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable { let data: any = {values: formValues}; if (outcome) { data.outcome = outcome; } let body = JSON.stringify(data); - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.completeTaskForm(taskId, body)) + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.completeTaskForm(taskId, body)) .catch(this.handleError); } @@ -170,8 +171,8 @@ export class FormService { * @param taskId Task Id * @returns {Observable} */ - public getTaskForm(taskId: string): Observable { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.taskApi.getTaskForm(taskId)) + getTaskForm(taskId: string): Observable { + return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTaskForm(taskId)) .map(this.toJson) .catch(this.handleError); } @@ -181,8 +182,8 @@ export class FormService { * @param formId Form Id * @returns {Observable} */ - public getFormDefinitionById(formId: string): Observable { - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.editorApi.getForm(formId)) + getFormDefinitionById(formId: string): Observable { + return Observable.fromPromise(this.apiService.getInstance().activiti.editorApi.getForm(formId)) .map(this.toJson) .catch(this.handleError); } @@ -192,23 +193,53 @@ export class FormService { * @param name * @returns {Promise|Promise} */ - public getFormDefinitionByName(name: string): Observable { + getFormDefinitionByName(name: string): Observable { let opts = { 'filter': 'myReusableForms', 'filterText': name, 'modelType': 2 }; - return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.modelsApi.getModels(opts)) + return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.getModels(opts)) .map(this.getFormId) .catch(this.handleError); } getRestFieldValues(taskId: string, field: string): Observable { - let alfrescoApi = this.authService.getAlfrescoApi(); + let alfrescoApi = this.apiService.getInstance(); return Observable.fromPromise(alfrescoApi.activiti.taskFormsApi.getRestFieldValues(taskId, field)); } + // TODO: uses private webApp api + getWorkflowGroups(filter: string): Observable { + return Observable.create(observer => { + + let xhr: XMLHttpRequest = new XMLHttpRequest(); + xhr.withCredentials = true; + + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let json = JSON.parse(xhr.response); + let data: GroupModel[] = (json.data || []).map(item => item); + // console.log(json); + observer.next(data); + observer.complete(); + } else { + console.error(xhr.response); + Observable.throw(new Error(xhr.response)); + } + } + }; + + let host = this.apiService.getInstance().config.hostBpm; + let url = `${host}/activiti-app/app/rest/workflow-groups?filter=${filter}`; + xhr.open('GET', url, true); + xhr.setRequestHeader('Authorization', this.apiService.getInstance().getTicketBpm()); + xhr.send(); + }); + } + getFormId(res: any) { let result = null;