diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html
index f89403d7f4..5b136d1bf1 100644
--- a/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/container/container.widget.html
@@ -49,6 +49,12 @@
+
+
+
+
UNKNOWN WIDGET TYPE: {{field.type}}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts
index bddb2e3dd2..a43aa1de2e 100644
--- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts
@@ -25,6 +25,8 @@ export class FormFieldTypes {
static READONLY_TEXT: string = 'readonly-text';
static UPLOAD: string = 'upload';
static TYPEAHEAD: string = 'typeahead';
+ static FUNCTIONAL_GROUP: string = 'functional-group';
+ static PEOPLE: string = 'people';
static READONLY_TYPES: string[] = [
FormFieldTypes.HYPERLINK,
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..66ee9f1154
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/group-user.model.ts
@@ -0,0 +1,35 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export class GroupUserModel {
+
+ company: string;
+ email: string;
+ firstName: string;
+ id: string;
+ lastName: string;
+
+ constructor(json?: any) {
+ if (json) {
+ this.company = json.company;
+ this.email = json.email;
+ this.firstName = json.firstName;
+ this.id = json.id;
+ this.lastName = json.lastName;
+ }
+ }
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts
new file mode 100644
index 0000000000..bf14a9d2a7
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/group.model.ts
@@ -0,0 +1,36 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export class GroupModel {
+
+ externalId: string;
+ groups: any;
+ id: string;
+ name: string;
+ status: string;
+
+ constructor(json?: any) {
+ if (json) {
+ this.externalId = json.externalId;
+ this.groups = json.groups;
+ this.id = json.id;
+ this.name = json.name;
+ this.status = json.status;
+ }
+ }
+
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css
new file mode 100644
index 0000000000..6533c16189
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.css
@@ -0,0 +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
new file mode 100644
index 0000000000..5562b58d6e
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts
new file mode 100644
index 0000000000..238d9d6739
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts
@@ -0,0 +1,176 @@
+/*!
+ * @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 { it, describe, expect, beforeEach } from '@angular/core/testing';
+import { Observable } from 'rxjs/Rx';
+import { FunctionalGroupWidget } from './functional-group.widget';
+import { FormService } from '../../../services/form.service';
+import { FormModel } from '../core/form.model';
+import { FormFieldModel } from '../core/form-field.model';
+import { GroupModel } from '../core/group.model';
+
+describe('FunctionalGroupWidget', () => {
+
+ let formService: FormService;
+ let widget: FunctionalGroupWidget;
+
+ beforeEach(() => {
+ formService = new FormService(null, null);
+ widget = new FunctionalGroupWidget(formService);
+ widget.field = new FormFieldModel(new FormModel());
+ });
+
+ it('should setup text from underlying field on init', () => {
+ let group = new GroupModel({ name: 'group-1'});
+ widget.field.value = group;
+ widget.ngOnInit();
+ expect(widget.value).toBe(group.name);
+ });
+
+ it('should not setup text on init', () => {
+ widget.field.value = null;
+ widget.ngOnInit();
+ expect(widget.value).toBeUndefined();
+ });
+
+ it('should flush value on blur', (done) => {
+ spyOn(widget, 'flushValue').and.stub();
+ widget.onBlur();
+
+ setTimeout(() => {
+ expect(widget.flushValue).toHaveBeenCalled();
+ done();
+ }, 200);
+ });
+
+ it('should prevent default behaviour on option item click', () => {
+ let event = jasmine.createSpyObj('event', ['preventDefault']);
+ widget.onItemClick(null, event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ });
+
+ it('should update values on item click', () => {
+ let item = new GroupModel({ name: 'group-1' });
+
+ widget.onItemClick(item, null);
+ expect(widget.field.value).toBe(item);
+ expect(widget.value).toBe(item.name);
+ });
+
+ it('should hide popup on flush', () => {
+ widget.popupVisible = true;
+ widget.flushValue();
+ expect(widget.popupVisible).toBeFalsy();
+ });
+
+ it('should update form on value flush', () => {
+ spyOn(widget.field, 'updateForm').and.callThrough();
+ widget.flushValue();
+ expect(widget.field.updateForm).toHaveBeenCalled();
+ });
+
+ it('should flush selected value', () => {
+ let groups: GroupModel[] = [
+ new GroupModel({ id: '1', name: 'group 1' }),
+ new GroupModel({ id: '2', name: 'group 2' })
+ ];
+
+ widget.groups = groups;
+ widget.value = 'group 2';
+ widget.flushValue();
+
+ expect(widget.value).toBe(groups[1].name);
+ expect(widget.field.value).toBe(groups[1]);
+ });
+
+ it('should be case insensitive when flushing value', () => {
+ let groups: GroupModel[] = [
+ new GroupModel({ id: '1', name: 'group 1' }),
+ new GroupModel({ id: '2', name: 'gRoUp 2' })
+ ];
+
+ widget.groups = groups;
+ widget.value = 'GROUP 2';
+ widget.flushValue();
+
+ expect(widget.value).toBe(groups[1].name);
+ expect(widget.field.value).toBe(groups[1]);
+ });
+
+ it('should hide popup on key up', () => {
+ widget.popupVisible = true;
+ widget.onKeyUp(null);
+ expect(widget.popupVisible).toBeFalsy();
+ });
+
+ it('should fetch groups and show popup on key up', () => {
+ let groups: GroupModel[] = [
+ new GroupModel(),
+ new GroupModel()
+ ];
+ spyOn(formService, 'getWorkflowGroups').and.returnValue(
+ Observable.create(observer => {
+ observer.next(groups);
+ observer.complete();
+ })
+ );
+
+ widget.value = 'group';
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowGroups).toHaveBeenCalledWith('group');
+ expect(widget.groups).toBe(groups);
+ expect(widget.popupVisible).toBeTruthy();
+ });
+
+ it('should hide popup when fetching empty group list', () => {
+ spyOn(formService, 'getWorkflowGroups').and.returnValue(
+ Observable.create(observer => {
+ observer.next(null);
+ observer.complete();
+ })
+ );
+
+ widget.value = 'group';
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowGroups).toHaveBeenCalledWith('group');
+ expect(widget.groups.length).toBe(0);
+ expect(widget.popupVisible).toBeFalsy();
+ });
+
+ it('should not fetch groups when value is missing', () => {
+ spyOn(formService, 'getWorkflowGroups').and.stub();
+
+ widget.value = null;
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowGroups).not.toHaveBeenCalled();
+ expect(widget.popupVisible).toBeFalsy();
+ });
+
+ it('should not fetch groups when value violates constraints', () => {
+ spyOn(formService, 'getWorkflowGroups').and.stub();
+
+ widget.minTermLength = 4;
+ widget.value = '123';
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowGroups).not.toHaveBeenCalled();
+ expect(widget.popupVisible).toBeFalsy();
+ });
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts
new file mode 100644
index 0000000000..001b6f264f
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts
@@ -0,0 +1,95 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { WidgetComponent } from './../widget.component';
+import { FormService } from '../../../services/form.service';
+import { GroupModel } from './../core/group.model';
+
+declare let __moduleName: string;
+
+@Component({
+ moduleId: __moduleName,
+ selector: 'functional-group-widget',
+ templateUrl: './functional-group.widget.html',
+ styleUrls: ['./functional-group.widget.css']
+})
+export class FunctionalGroupWidget extends WidgetComponent implements OnInit {
+
+ value: string;
+ popupVisible: boolean = false;
+ groups: GroupModel[] = [];
+ minTermLength: number = 1;
+
+ 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/index.ts b/ng2-components/ng2-activiti-form/src/components/widgets/index.ts
index 32d1c765ac..47846bc631 100644
--- a/ng2-components/ng2-activiti-form/src/components/widgets/index.ts
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/index.ts
@@ -29,6 +29,8 @@ import { DisplayValueWidget } from './display-value/display-value.widget';
import { DisplayTextWidget } from './display-text/display-text.widget';
import { UploadWidget } from './upload/upload.widget';
import { TypeaheadWidget } from './typeahead/typeahead.widget';
+import { FunctionalGroupWidget } from './functional-group/functional-group.widget';
+import { PeopleWidget } from './people/people.widget';
// core
export * from './widget.component';
@@ -50,6 +52,8 @@ export * from './display-value/display-value.widget';
export * from './display-text/display-text.widget';
export * from './upload/upload.widget';
export * from './typeahead/typeahead.widget';
+export * from './functional-group/functional-group.widget';
+export * from './people/people.widget';
export const CONTAINER_WIDGET_DIRECTIVES: [any] = [
TabsWidget,
@@ -67,7 +71,9 @@ export const PRIMITIVE_WIDGET_DIRECTIVES: [any] = [
DisplayValueWidget,
DisplayTextWidget,
UploadWidget,
- TypeaheadWidget
+ TypeaheadWidget,
+ FunctionalGroupWidget,
+ PeopleWidget
];
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.css b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.css
new file mode 100644
index 0000000000..de35519d8b
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.css
@@ -0,0 +1,29 @@
+.people-widget {
+ width: 100%;
+}
+
+.people-widget--autocomplete {
+ background-color: #fff;
+ position: absolute;
+ z-index: 5;
+ color: #555;
+ margin: -15px 0 0 0;
+}
+
+.people-widget--autocomplete > ul {
+ list-style-type: none;
+ position: static;
+
+ height: auto;
+ width: auto;
+ min-width: 124px;
+ padding: 8px 0;
+ margin: 0;
+
+ box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
+ border-radius: 2px;
+}
+
+.people-widget--autocomplete > ul > li {
+ opacity: 1;
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.html
new file mode 100644
index 0000000000..0d660b2bb4
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts
new file mode 100644
index 0000000000..c20fe413fa
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts
@@ -0,0 +1,208 @@
+/*!
+ * @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 { it, describe, expect, beforeEach } from '@angular/core/testing';
+import { Observable } from 'rxjs/Rx';
+import { PeopleWidget } from './people.widget';
+import { FormService } from '../../../services/form.service';
+import { FormModel } from '../core/form.model';
+import { FormFieldModel } from '../core/form-field.model';
+// import { GroupModel } from '../core/group.model';
+import { GroupUserModel } from '../core/group-user.model';
+
+describe('PeopleWidget', () => {
+
+ let formService: FormService;
+ let widget: PeopleWidget;
+
+ beforeEach(() => {
+ formService = new FormService(null, null);
+ widget = new PeopleWidget(formService);
+ widget.field = new FormFieldModel(new FormModel());
+ });
+
+ it('should return empty display name for missing model', () => {
+ expect(widget.getDisplayName(null)).toBe('');
+ });
+
+ it('should return full name for a given model', () => {
+ let model = new GroupUserModel({
+ firstName: 'John',
+ lastName: 'Doe'
+ });
+ expect(widget.getDisplayName(model)).toBe('John Doe');
+ });
+
+ it('should flush value on blur', (done) => {
+ spyOn(widget, 'flushValue').and.stub();
+ widget.onBlur();
+
+ setTimeout(() => {
+ expect(widget.flushValue).toHaveBeenCalled();
+ done();
+ }, 200);
+ });
+
+ it('should init value from the field', () => {
+ widget.field.value = new GroupUserModel({
+ firstName: 'John',
+ lastName: 'Doe'
+ });
+ widget.ngOnInit();
+ expect(widget.value).toBe('John Doe');
+ });
+
+ it('should prevent default behaviour on option item click', () => {
+ let event = jasmine.createSpyObj('event', ['preventDefault']);
+ widget.onItemClick(null, event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ });
+
+ it('should update values on item click', () => {
+ let item = new GroupUserModel({ firstName: 'John', lastName: 'Doe' });
+
+ widget.onItemClick(item, null);
+ expect(widget.field.value).toBe(item);
+ expect(widget.value).toBe('John Doe');
+ });
+
+ it('should require form field to setup values on init', () => {
+ widget.field = null;
+ widget.ngOnInit();
+
+ expect(widget.value).toBeUndefined();
+ expect(widget.groupId).toBeUndefined();
+ });
+
+ it('should setup group restriction', () => {
+ widget.ngOnInit();
+ expect(widget.groupId).toBeUndefined();
+
+ widget.field.params = { restrictWithGroup: { id: '' } };
+ widget.ngOnInit();
+ expect(widget.groupId).toBe('');
+ });
+
+ it('should fetch users by search term', () => {
+ let users = [{}, {}];
+ spyOn(formService, 'getWorkflowUsers').and.returnValue(Observable.create(observer => {
+ observer.next(users);
+ observer.complete();
+ }));
+
+ widget.value = 'user1';
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowUsers).toHaveBeenCalledWith(widget.value, widget.groupId);
+ expect(widget.users).toBe(users);
+ expect(widget.popupVisible).toBeTruthy();
+ });
+
+ it('should fetch users by search term and group id', () => {
+ let users = [{}, {}];
+ spyOn(formService, 'getWorkflowUsers').and.returnValue(Observable.create(observer => {
+ observer.next(users);
+ observer.complete();
+ }));
+
+ widget.value = 'user1';
+ widget.groupId = '1001';
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowUsers).toHaveBeenCalledWith(widget.value, widget.groupId);
+ expect(widget.users).toBe(users);
+ expect(widget.popupVisible).toBeTruthy();
+ });
+
+ it('should fetch users and show no popup', () => {
+ spyOn(formService, 'getWorkflowUsers').and.returnValue(Observable.create(observer => {
+ observer.next(null);
+ observer.complete();
+ }));
+
+ widget.value = 'user1';
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowUsers).toHaveBeenCalledWith(widget.value, widget.groupId);
+ expect(widget.users).toEqual([]);
+ expect(widget.popupVisible).toBeFalsy();
+ });
+
+ it('should require search term to fetch users', () => {
+ spyOn(formService, 'getWorkflowUsers').and.stub();
+
+ widget.value = null;
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowUsers).not.toHaveBeenCalled();
+ });
+
+ it('should not fetch users due to constraint violation', () => {
+ spyOn(formService, 'getWorkflowUsers').and.stub();
+
+ widget.value = '123';
+ widget.minTermLength = 4;
+ widget.onKeyUp(null);
+
+ expect(formService.getWorkflowUsers).not.toHaveBeenCalled();
+ });
+
+ it('should hide popup on value flush', () => {
+ widget.popupVisible = true;
+ widget.flushValue();
+ expect(widget.popupVisible).toBeFalsy();
+ });
+
+ it('should update form on value flush', () => {
+ spyOn(widget.field, 'updateForm').and.callThrough();
+ widget.flushValue();
+ expect(widget.field.updateForm).toHaveBeenCalled();
+ });
+
+ it('should flush value and update field', () => {
+ widget.users = [
+ new GroupUserModel({ firstName: 'Tony', lastName: 'Stark' }),
+ new GroupUserModel({ firstName: 'John', lastName: 'Doe' })
+ ];
+ widget.value = 'John Doe';
+ widget.flushValue();
+
+ expect(widget.value).toBe('John Doe');
+ expect(widget.field.value).toBe(widget.users[1]);
+ });
+
+ it('should be case insensitive when flushing field', () => {
+ widget.users = [
+ new GroupUserModel({ firstName: 'Tony', lastName: 'Stark' }),
+ new GroupUserModel({ firstName: 'John', lastName: 'Doe' })
+ ];
+ widget.value = 'TONY sTaRk';
+ widget.flushValue();
+
+ expect(widget.value).toBe('Tony Stark');
+ expect(widget.field.value).toBe(widget.users[0]);
+ });
+
+ it('should reset value and field on flush', () => {
+ widget.value = 'Missing User';
+ widget.field.value = {};
+ widget.flushValue();
+
+ expect(widget.value).toBeNull();
+ expect(widget.field.value).toBeNull();
+ });
+});
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts
new file mode 100644
index 0000000000..9eb2a468f6
--- /dev/null
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts
@@ -0,0 +1,117 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { WidgetComponent } from './../widget.component';
+import { FormService } from '../../../services/form.service';
+import { GroupModel } from '../core/group.model';
+import { GroupUserModel } from '../core/group-user.model';
+
+declare let __moduleName: string;
+
+@Component({
+ moduleId: __moduleName,
+ selector: 'people-widget',
+ templateUrl: './people.widget.html',
+ styleUrls: ['./people.widget.css']
+})
+export class PeopleWidget extends WidgetComponent implements OnInit {
+
+ popupVisible: boolean = false;
+ minTermLength: number = 1;
+ value: string;
+ users: GroupUserModel[] = [];
+ groupId: string;
+
+ constructor(private formService: FormService) {
+ super();
+ }
+
+ // TODO: investigate, called 2 times
+ // https://github.com/angular/angular/issues/6782
+ ngOnInit() {
+ if (this.field) {
+ let user: GroupUserModel = this.field.value;
+ if (user) {
+ this.value = this.getDisplayName(user);
+ }
+
+ let params = this.field.params;
+ if (params && params['restrictWithGroup']) {
+ let restrictWithGroup = params['restrictWithGroup'];
+ this.groupId = restrictWithGroup.id;
+ }
+ }
+ }
+
+ onKeyUp(event: KeyboardEvent) {
+ if (this.value && this.value.length >= this.minTermLength) {
+ this.formService.getWorkflowUsers(this.value, this.groupId)
+ .subscribe((result: GroupUserModel[]) => {
+ this.users = result || [];
+ this.popupVisible = this.users.length > 0;
+ });
+ } else {
+ this.popupVisible = false;
+ }
+ }
+
+ onBlur() {
+ setTimeout(() => {
+ this.flushValue();
+ }, 200);
+ }
+
+ flushValue() {
+ this.popupVisible = false;
+
+ let option = this.users.find(item => {
+ let fullName = this.getDisplayName(item).toLocaleLowerCase();
+ return fullName === this.value.toLocaleLowerCase();
+ });
+
+ if (option) {
+ this.field.value = option;
+ this.value = this.getDisplayName(option);
+ } else {
+ this.field.value = null;
+ this.value = null;
+ }
+
+ this.field.updateForm();
+ }
+
+ getDisplayName(model: GroupUserModel) {
+ if (model) {
+ let displayName = `${model.firstName} ${model.lastName}`;
+ return displayName.trim();
+ }
+
+ return '';
+ }
+
+ // TODO: still causes onBlur execution
+ onItemClick(item: GroupUserModel, event: Event) {
+ if (item) {
+ this.field.value = item;
+ this.value = this.getDisplayName(item);
+ }
+ if (event) {
+ event.preventDefault();
+ }
+ }
+}
diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.html
index ab43e475a6..332465da94 100644
--- a/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.html
+++ b/ng2-components/ng2-activiti-form/src/components/widgets/typeahead/typeahead.widget.html
@@ -11,9 +11,9 @@
-