[CST-3088] Created abstract form component to handle vocabularies

This commit is contained in:
Giuseppe Digilio
2020-06-29 22:20:52 +02:00
parent 4cc1a3eecd
commit 3225966600
15 changed files with 649 additions and 250 deletions

View File

@@ -0,0 +1,119 @@
import { EventEmitter, Input, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
import {
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { map } from 'rxjs/operators';
import { Observable, of as observableOf } from 'rxjs';
import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service';
import { isNotEmpty } from '../../../../empty.util';
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
import { VocabularyEntry } from '../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
import { DsDynamicInputModel } from './ds-dynamic-input.model';
import { PageInfo } from '../../../../../core/shared/page-info.model';
/**
* An abstract class to be extended by form components that handle vocabulary
*/
export abstract class DsDynamicVocabularyComponent extends DynamicFormControlComponent {
@Input() abstract bindId = true;
@Input() abstract group: FormGroup;
@Input() abstract model: DsDynamicInputModel;
@Output() abstract blur: EventEmitter<any> = new EventEmitter<any>();
@Output() abstract change: EventEmitter<any> = new EventEmitter<any>();
@Output() abstract focus: EventEmitter<any> = new EventEmitter<any>();
public abstract pageInfo: PageInfo;
constructor(protected vocabularyService: VocabularyService,
protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService
) {
super(layoutService, validationService);
}
/**
* Sets the current value with the given value.
* @param value The value to set.
* @param init Representing if is init value or not.
*/
public abstract setCurrentValue(value: any, init?: boolean);
/**
* Retrieves the init form value from model
*/
getInitValueFromModel(): Observable<FormFieldMetadataValueObject> {
let initValue$: Observable<FormFieldMetadataValueObject>;
if (isNotEmpty(this.model.value) && (this.model.value instanceof FormFieldMetadataValueObject)) {
let initEntry$: Observable<VocabularyEntry>;
if (this.model.value.hasAuthority()) {
initEntry$ = this.vocabularyService.getVocabularyEntryByID(this.model.value.authority, this.model.vocabularyOptions)
} else {
initEntry$ = this.vocabularyService.getVocabularyEntryByValue(this.model.value.value, this.model.vocabularyOptions)
}
initValue$ = initEntry$.pipe(map((initEntry: VocabularyEntry) => {
if (isNotEmpty(initEntry)) {
return new FormFieldMetadataValueObject(
initEntry.value,
null,
initEntry.authority,
initEntry.display
);
} else {
return this.model.value as any;
}
}));
} else {
initValue$ = observableOf(new FormFieldMetadataValueObject(this.model.value));
}
return initValue$;
}
/**
* Emits a blur event containing a given value.
* @param event The value to emit.
*/
onBlur(event: Event) {
this.blur.emit(event);
}
/**
* Emits a focus event containing a given value.
* @param event The value to emit.
*/
onFocus(event) {
this.focus.emit(event);
}
/**
* Emits a change event and updates model value.
* @param updateValue
*/
dispatchUpdate(updateValue: any) {
this.model.valueUpdates.next(updateValue);
this.change.emit(updateValue);
}
/**
* Update the page info object
* @param elementsPerPage
* @param currentPage
* @param totalElements
* @param totalPages
*/
protected updatePageInfo(elementsPerPage: number, currentPage: number, totalElements?: number, totalPages?: number) {
this.pageInfo = Object.assign(new PageInfo(), {
elementsPerPage: elementsPerPage,
currentPage: currentPage,
totalElements: totalElements,
totalPages: totalPages
});
}
}

View File

@@ -9,7 +9,6 @@ import {
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { findKey } from 'lodash'; import { findKey } from 'lodash';
import { VocabularyFindOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-find-options.model';
import { hasValue, isNotEmpty } from '../../../../../empty.util'; import { hasValue, isNotEmpty } from '../../../../../empty.util';
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model'; import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
import { FormBuilderService } from '../../../form-builder.service'; import { FormBuilderService } from '../../../form-builder.service';
@@ -18,6 +17,7 @@ import { VocabularyService } from '../../../../../../core/submission/vocabularie
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators'; import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
import { PageInfo } from '../../../../../../core/shared/page-info.model';
export interface ListItem { export interface ListItem {
id: string, id: string,
@@ -26,12 +26,14 @@ export interface ListItem {
index: number index: number
} }
/**
* Component representing a list input field
*/
@Component({ @Component({
selector: 'ds-dynamic-list', selector: 'ds-dynamic-list',
styleUrls: ['./dynamic-list.component.scss'], styleUrls: ['./dynamic-list.component.scss'],
templateUrl: './dynamic-list.component.html' templateUrl: './dynamic-list.component.html'
}) })
export class DsDynamicListComponent extends DynamicFormControlComponent implements OnInit { export class DsDynamicListComponent extends DynamicFormControlComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@@ -43,7 +45,6 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
public items: ListItem[][] = []; public items: ListItem[][] = [];
protected optionsList: VocabularyEntry[]; protected optionsList: VocabularyEntry[];
protected searchOptions: VocabularyFindOptions;
constructor(private vocabularyService: VocabularyService, constructor(private vocabularyService: VocabularyService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
@@ -56,14 +57,6 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
ngOnInit() { ngOnInit() {
if (this.hasAuthorityOptions()) { if (this.hasAuthorityOptions()) {
// TODO Replace max elements 1000 with a paginated request when pagination bug is resolved
this.searchOptions = new VocabularyFindOptions(
this.model.vocabularyOptions.scope,
this.model.vocabularyOptions.name,
this.model.vocabularyOptions.metadata,
'',
1000, // Max elements
1);// Current Page
this.setOptionsFromAuthority(); this.setOptionsFromAuthority();
} }
} }
@@ -99,7 +92,10 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
protected setOptionsFromAuthority() { protected setOptionsFromAuthority() {
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) { if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
const listGroup = this.group.controls[this.model.id] as FormGroup; const listGroup = this.group.controls[this.model.id] as FormGroup;
this.vocabularyService.getVocabularyEntries(this.searchOptions).pipe( const pageInfo: PageInfo = new PageInfo({
elementsPerPage: Number.MAX_VALUE, currentPage: 1
} as PageInfo);
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, pageInfo).pipe(
getFirstSucceededRemoteDataPayload() getFirstSucceededRemoteDataPayload()
).subscribe((entries: PaginatedList<VocabularyEntry>) => { ).subscribe((entries: PaginatedList<VocabularyEntry>) => {
let groupCounter = 0; let groupCounter = 0;

View File

@@ -21,8 +21,8 @@
[placeholder]="model.placeholder | translate" [placeholder]="model.placeholder | translate"
[readonly]="model.readOnly" [readonly]="model.readOnly"
(change)="onChange($event)" (change)="onChange($event)"
(blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();" (blur)="onBlur($event); $event.stopPropagation(); sdRef.close();"
(focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();" (focus)="onFocus($event); $event.stopPropagation(); sdRef.close();"
(click)="$event.stopPropagation(); $event.stopPropagation(); sdRef.close();"> (click)="$event.stopPropagation(); $event.stopPropagation(); sdRef.close();">
</div> </div>
@@ -40,8 +40,8 @@
[placeholder]="model.secondPlaceholder | translate" [placeholder]="model.secondPlaceholder | translate"
[readonly]="model.readOnly" [readonly]="model.readOnly"
(change)="onChange($event)" (change)="onChange($event)"
(blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();" (blur)="onBlur($event); $event.stopPropagation(); sdRef.close();"
(focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();" (focus)="onFocus($event); $event.stopPropagation(); sdRef.close();"
(click)="$event.stopPropagation(); sdRef.close();"> (click)="$event.stopPropagation(); sdRef.close();">
</div> </div>
<div class="col-auto text-center"> <div class="col-auto text-center">

View File

@@ -4,6 +4,7 @@ import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angul
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing'; import { async, ComponentFixture, fakeAsync, inject, TestBed, tick, } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { of as observableOf } from 'rxjs';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InfiniteScrollModule } from 'ngx-infinite-scroll';
@@ -309,7 +310,13 @@ describe('Dynamic Lookup component', () => {
lookupComp = lookupFixture.componentInstance; // FormComponent test instance lookupComp = lookupFixture.componentInstance; // FormComponent test instance
lookupComp.group = LOOKUP_TEST_GROUP; lookupComp.group = LOOKUP_TEST_GROUP;
lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG); lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG);
lookupComp.model.value = new FormFieldMetadataValueObject('test', null, 'test001'); const entry = observableOf(Object.assign(new VocabularyEntry(), {
authority: null,
value: 'test',
display: 'testDisplay'
}));
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
(lookupComp.model as any).value = new FormFieldMetadataValueObject('test', null, null, 'testDisplay');
lookupFixture.detectChanges(); lookupFixture.detectChanges();
// spyOn(store, 'dispatch'); // spyOn(store, 'dispatch');
@@ -318,9 +325,52 @@ describe('Dynamic Lookup component', () => {
lookupFixture.destroy(); lookupFixture.destroy();
lookupComp = null; lookupComp = null;
}); });
it('should init component properly', () => { it('should init component properly', fakeAsync(() => {
expect(lookupComp.firstInputValue).toBe('test'); tick();
expect(lookupComp.firstInputValue).toBe('testDisplay');
expect((lookupComp as any).vocabularyService.getVocabularyEntryByValue).toHaveBeenCalled();
}));
it('should have search button disabled on edit mode', () => {
lookupComp.editMode = true;
lookupFixture.detectChanges();
const de = lookupFixture.debugElement.queryAll(By.css('button'));
const searchBtnEl = de[0].nativeElement;
const saveBtnEl = de[1].nativeElement;
expect(searchBtnEl.disabled).toBe(true);
expect(saveBtnEl.disabled).toBe(false);
expect(saveBtnEl.textContent.trim()).toBe('form.save');
}); });
});
describe('and init model value is not empty with authority', () => {
beforeEach(() => {
lookupFixture = TestBed.createComponent(DsDynamicLookupComponent);
lookupComp = lookupFixture.componentInstance; // FormComponent test instance
lookupComp.group = LOOKUP_TEST_GROUP;
lookupComp.model = new DynamicLookupModel(LOOKUP_TEST_MODEL_CONFIG);
const entry = observableOf(Object.assign(new VocabularyEntry(), {
authority: 'test001',
value: 'test',
display: 'testDisplay'
}));
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
lookupComp.model.value = new FormFieldMetadataValueObject('test', null, 'test001', 'testDisplay');
lookupFixture.detectChanges();
// spyOn(store, 'dispatch');
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', fakeAsync(() => {
tick();
expect(lookupComp.firstInputValue).toBe('testDisplay');
expect((lookupComp as any).vocabularyService.getVocabularyEntryByID).toHaveBeenCalled();
}));
it('should have search button disabled on edit mode', () => { it('should have search button disabled on edit mode', () => {
lookupComp.editMode = true; lookupComp.editMode = true;
@@ -430,6 +480,13 @@ describe('Dynamic Lookup component', () => {
lookupComp.group = LOOKUP_TEST_GROUP; lookupComp.group = LOOKUP_TEST_GROUP;
lookupComp.model = new DynamicLookupNameModel(LOOKUP_NAME_TEST_MODEL_CONFIG); lookupComp.model = new DynamicLookupNameModel(LOOKUP_NAME_TEST_MODEL_CONFIG);
lookupComp.model.value = new FormFieldMetadataValueObject('Name, Lastname', null, 'test001'); lookupComp.model.value = new FormFieldMetadataValueObject('Name, Lastname', null, 'test001');
const entry = observableOf(Object.assign(new VocabularyEntry(), {
authority: null,
value: 'Name, Lastname',
display: 'Name, Lastname'
}));
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
(lookupComp.model as any).value = new FormFieldMetadataValueObject('Name, Lastname', null, null, 'Name, Lastname');
lookupFixture.detectChanges(); lookupFixture.detectChanges();
}); });
@@ -437,10 +494,55 @@ describe('Dynamic Lookup component', () => {
lookupFixture.destroy(); lookupFixture.destroy();
lookupComp = null; lookupComp = null;
}); });
it('should init component properly', () => { it('should init component properly', fakeAsync(() => {
tick();
expect(lookupComp.firstInputValue).toBe('Name'); expect(lookupComp.firstInputValue).toBe('Name');
expect(lookupComp.secondInputValue).toBe('Lastname'); expect(lookupComp.secondInputValue).toBe('Lastname');
expect((lookupComp as any).vocabularyService.getVocabularyEntryByValue).toHaveBeenCalled();
}));
it('should have search button disabled on edit mode', () => {
lookupComp.editMode = true;
lookupFixture.detectChanges();
const de = lookupFixture.debugElement.queryAll(By.css('button'));
const searchBtnEl = de[0].nativeElement;
const saveBtnEl = de[1].nativeElement;
expect(searchBtnEl.disabled).toBe(true);
expect(saveBtnEl.disabled).toBe(false);
expect(saveBtnEl.textContent.trim()).toBe('form.save');
}); });
});
describe('and init model value is not empty with authority', () => {
beforeEach(() => {
lookupFixture = TestBed.createComponent(DsDynamicLookupComponent);
lookupComp = lookupFixture.componentInstance; // FormComponent test instance
lookupComp.group = LOOKUP_TEST_GROUP;
lookupComp.model = new DynamicLookupNameModel(LOOKUP_NAME_TEST_MODEL_CONFIG);
lookupComp.model.value = new FormFieldMetadataValueObject('Name, Lastname', null, 'test001');
const entry = observableOf(Object.assign(new VocabularyEntry(), {
authority: 'test001',
value: 'Name, Lastname',
display: 'Name, Lastname'
}));
spyOn((lookupComp as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
lookupComp.model.value = new FormFieldMetadataValueObject('Name, Lastname', null, 'test001', 'Name, Lastname');
lookupFixture.detectChanges();
});
afterEach(() => {
lookupFixture.destroy();
lookupComp = null;
});
it('should init component properly', fakeAsync(() => {
tick();
expect(lookupComp.firstInputValue).toBe('Name');
expect(lookupComp.secondInputValue).toBe('Lastname');
expect((lookupComp as any).vocabularyService.getVocabularyEntryByID).toHaveBeenCalled();
}));
it('should have search button disabled on edit mode', () => { it('should have search button disabled on edit mode', () => {
lookupComp.editMode = true; lookupComp.editMode = true;

View File

@@ -4,15 +4,10 @@ import { FormGroup } from '@angular/forms';
import { of as observableOf, Subscription } from 'rxjs'; import { of as observableOf, Subscription } from 'rxjs';
import { catchError, distinctUntilChanged } from 'rxjs/operators'; import { catchError, distinctUntilChanged } from 'rxjs/operators';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import { import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
import { VocabularyFindOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-find-options.model'; import { hasValue, isEmpty, isNotEmpty } from '../../../../../empty.util';
import { hasValue, isEmpty, isNotEmpty, isNull, isUndefined } from '../../../../../empty.util';
import { PageInfo } from '../../../../../../core/shared/page-info.model'; import { PageInfo } from '../../../../../../core/shared/page-info.model';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
@@ -20,16 +15,21 @@ import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
import { ConfidenceType } from '../../../../../../core/shared/confidence-type'; import { ConfidenceType } from '../../../../../../core/shared/confidence-type';
import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators'; import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
import { DynamicLookupModel } from './dynamic-lookup.model';
/**
* Component representing a lookup or lookup-name input field
*/
@Component({ @Component({
selector: 'ds-dynamic-lookup', selector: 'ds-dynamic-lookup',
styleUrls: ['./dynamic-lookup.component.scss'], styleUrls: ['./dynamic-lookup.component.scss'],
templateUrl: './dynamic-lookup.component.html' templateUrl: './dynamic-lookup.component.html'
}) })
export class DsDynamicLookupComponent extends DynamicFormControlComponent implements OnDestroy, OnInit { export class DsDynamicLookupComponent extends DsDynamicVocabularyComponent implements OnDestroy, OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: any; @Input() model: DynamicLookupModel | DynamicLookupNameModel;
@Output() blur: EventEmitter<any> = new EventEmitter<any>(); @Output() blur: EventEmitter<any> = new EventEmitter<any>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
@@ -42,89 +42,97 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
public pageInfo: PageInfo; public pageInfo: PageInfo;
public optionsList: any; public optionsList: any;
protected searchOptions: VocabularyFindOptions;
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
constructor(private vocabularyService: VocabularyService, constructor(protected vocabularyService: VocabularyService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService, protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService protected validationService: DynamicFormValidationService
) { ) {
super(layoutService, validationService); super(vocabularyService, layoutService, validationService);
} }
/**
* Converts an item from the result list to a `string` to display in the `<input>` field.
*/
inputFormatter = (x: { display: string }, y: number) => { inputFormatter = (x: { display: string }, y: number) => {
return y === 1 ? this.firstInputValue : this.secondInputValue; return y === 1 ? this.firstInputValue : this.secondInputValue;
}; };
/**
* Initialize the component, setting up the init form value
*/
ngOnInit() { ngOnInit() {
this.searchOptions = new VocabularyFindOptions( if (isNotEmpty(this.model.value)) {
this.model.vocabularyOptions.scope, this.setCurrentValue(this.model.value, true);
this.model.vocabularyOptions.name, }
this.model.vocabularyOptions.metadata,
'',
this.model.maxOptions,
1);
this.setInputsValue(this.model.value);
this.subs.push(this.model.valueUpdates this.subs.push(this.model.valueUpdates
.subscribe((value) => { .subscribe((value) => {
if (isEmpty(value)) { if (isEmpty(value)) {
this.resetFields(); this.resetFields();
} else if (!this.editMode) { } else if (!this.editMode) {
this.setInputsValue(this.model.value); this.setCurrentValue(this.model.value);
} }
})); }));
} }
public formatItemForInput(item: any, field: number): string { /**
if (isUndefined(item) || isNull(item)) { * Check if model value has an authority
return ''; */
}
return (typeof item === 'string') ? item : this.inputFormatter(item, field);
}
public hasAuthorityValue() { public hasAuthorityValue() {
return hasValue(this.model.value) return hasValue(this.model.value)
&& this.model.value.hasAuthority(); && this.model.value.hasAuthority();
} }
/**
* Check if current value has an authority
*/
public hasEmptyValue() { public hasEmptyValue() {
return isNotEmpty(this.getCurrentValue()); return isNotEmpty(this.getCurrentValue());
} }
/**
* Clear inputs whether there is no results and authority is closed
*/
public clearFields() { public clearFields() {
// Clear inputs whether there is no results and authority is closed
if (this.model.vocabularyOptions.closed) { if (this.model.vocabularyOptions.closed) {
this.resetFields(); this.resetFields();
} }
} }
/**
* Check if edit button is disabled
*/
public isEditDisabled() { public isEditDisabled() {
return !this.hasAuthorityValue(); return !this.hasAuthorityValue();
} }
/**
* Check if input is disabled
*/
public isInputDisabled() { public isInputDisabled() {
return (this.model.vocabularyOptions.closed && this.hasAuthorityValue() && !this.editMode); return (this.model.vocabularyOptions.closed && this.hasAuthorityValue() && !this.editMode);
} }
/**
* Check if model is instanceof DynamicLookupNameModel
*/
public isLookupName() { public isLookupName() {
return (this.model instanceof DynamicLookupNameModel); return (this.model instanceof DynamicLookupNameModel);
} }
/**
* Check if search button is disabled
*/
public isSearchDisabled() { public isSearchDisabled() {
return isEmpty(this.firstInputValue) || this.editMode; return isEmpty(this.firstInputValue) || this.editMode;
} }
public onBlurEvent(event: Event) { /**
this.blur.emit(event); * Update model value with the typed text if vocabulary is not closed
} * @param event the typed text
*/
public onFocusEvent(event) {
this.focus.emit(event);
}
public onChange(event) { public onChange(event) {
event.preventDefault(); event.preventDefault();
if (!this.model.vocabularyOptions.closed) { if (!this.model.vocabularyOptions.closed) {
@@ -139,31 +147,51 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
} }
} }
/**
* Load more result entries
*/
public onScroll() { public onScroll() {
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) { if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
this.searchOptions.currentPage++; this.updatePageInfo(
this.pageInfo.elementsPerPage,
this.pageInfo.currentPage + 1,
this.pageInfo.totalElements,
this.pageInfo.totalPages
);
this.search(); this.search();
} }
} }
/**
* Update model value with selected entry
* @param event the selected entry
*/
public onSelect(event) { public onSelect(event) {
this.updateModel(event); this.updateModel(event);
} }
/**
* Reset the current value when dropdown toggle
*/
public openChange(isOpened: boolean) { public openChange(isOpened: boolean) {
if (!isOpened) { if (!isOpened) {
if (this.model.vocabularyOptions.closed && !this.hasAuthorityValue()) { if (this.model.vocabularyOptions.closed && !this.hasAuthorityValue()) {
this.setInputsValue(''); this.setCurrentValue('');
} }
} }
} }
/**
* Reset the model value
*/
public remove() { public remove() {
this.group.markAsPristine(); this.group.markAsPristine();
this.model.valueUpdates.next(null); this.dispatchUpdate(null)
this.change.emit(null);
} }
/**
* Saves all changes
*/
public saveChanges() { public saveChanges() {
if (isNotEmpty(this.getCurrentValue())) { if (isNotEmpty(this.getCurrentValue())) {
const newValue = Object.assign(new VocabularyEntry(), this.model.value, { const newValue = Object.assign(new VocabularyEntry(), this.model.value, {
@@ -177,15 +205,21 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
this.switchEditMode(); this.switchEditMode();
} }
/**
* Converts a stream of text values from the `<input>` element to the stream of the array of items
* to display in the result list.
*/
public search() { public search() {
this.optionsList = null; this.optionsList = null;
this.pageInfo = null; this.updatePageInfo(this.model.maxOptions, 1);
// Query
this.searchOptions.query = this.getCurrentValue();
this.loading = true; this.loading = true;
this.subs.push(this.vocabularyService.getVocabularyEntries(this.searchOptions).pipe(
this.subs.push(this.vocabularyService.getVocabularyEntriesByValue(
this.getCurrentValue(),
false,
this.model.vocabularyOptions,
this.pageInfo
).pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
catchError(() => catchError(() =>
observableOf(new PaginatedList( observableOf(new PaginatedList(
@@ -195,18 +229,28 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
), ),
distinctUntilChanged()) distinctUntilChanged())
.subscribe((list: PaginatedList<VocabularyEntry>) => { .subscribe((list: PaginatedList<VocabularyEntry>) => {
console.log(list);
this.optionsList = list.page; this.optionsList = list.page;
this.pageInfo = list.pageInfo; this.updatePageInfo(
list.pageInfo.elementsPerPage,
list.pageInfo.currentPage,
list.pageInfo.totalElements,
list.pageInfo.totalPages
);
this.loading = false; this.loading = false;
this.cdr.detectChanges(); this.cdr.detectChanges();
})); }));
} }
/**
* Changes the edit mode flag
*/
public switchEditMode() { public switchEditMode() {
this.editMode = !this.editMode; this.editMode = !this.editMode;
} }
/**
* Callback functions for whenClickOnConfidenceNotAccepted event
*/
public whenClickOnConfidenceNotAccepted(sdRef: NgbDropdown, confidence: ConfidenceType) { public whenClickOnConfidenceNotAccepted(sdRef: NgbDropdown, confidence: ConfidenceType) {
if (!this.model.readOnly) { if (!this.model.readOnly) {
sdRef.open(); sdRef.open();
@@ -220,6 +264,38 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
.forEach((sub) => sub.unsubscribe()); .forEach((sub) => sub.unsubscribe());
} }
/**
* Sets the current value with the given value.
* @param value The value to set.
* @param init Representing if is init value or not.
*/
public setCurrentValue(value: any, init = false) {
if (init) {
this.getInitValueFromModel()
.subscribe((value: FormFieldMetadataValueObject) => this.setDisplayInputValue(value.display));
} else if (hasValue(value)) {
if (value instanceof FormFieldMetadataValueObject || value instanceof VocabularyEntry) {
this.setDisplayInputValue(value.display);
}
}
}
protected setDisplayInputValue(displayValue: string) {
if (hasValue(displayValue)) {
if (this.isLookupName()) {
const values = displayValue.split((this.model as DynamicLookupNameModel).separator);
this.firstInputValue = (values[0] || '').trim();
this.secondInputValue = (values[1] || '').trim();
} else {
this.firstInputValue = displayValue || '';
}
}
}
/**
* Gets the current text present in the input field(s)
*/
protected getCurrentValue(): string { protected getCurrentValue(): string {
let result = ''; let result = '';
if (!this.isLookupName()) { if (!this.isLookupName()) {
@@ -237,6 +313,9 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
return result; return result;
} }
/**
* Clear text present in the input field(s)
*/
protected resetFields() { protected resetFields() {
this.firstInputValue = ''; this.firstInputValue = '';
if (this.isLookupName()) { if (this.isLookupName()) {
@@ -244,32 +323,12 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem
} }
} }
protected setInputsValue(value) {
if (hasValue(value)) {
let displayValue = value;
if (value instanceof FormFieldMetadataValueObject || value instanceof VocabularyEntry) {
displayValue = value.display;
}
if (hasValue(displayValue)) {
if (this.isLookupName()) {
const values = displayValue.split((this.model as DynamicLookupNameModel).separator);
this.firstInputValue = (values[0] || '').trim();
this.secondInputValue = (values[1] || '').trim();
} else {
this.firstInputValue = displayValue || '';
}
}
}
}
protected updateModel(value) { protected updateModel(value) {
this.group.markAsDirty(); this.group.markAsDirty();
this.model.valueUpdates.next(value); this.dispatchUpdate(value);
this.setInputsValue(value); this.setCurrentValue(value);
this.change.emit(value);
this.optionsList = null; this.optionsList = null;
this.pageInfo = null; this.pageInfo = null;
} }
} }

View File

@@ -23,13 +23,15 @@ import { hasValue, isEmpty, isNotEmpty, isNotNull } from '../../../../../empty.u
import { shrinkInOut } from '../../../../../animations/shrink'; import { shrinkInOut } from '../../../../../animations/shrink';
import { ChipsItem } from '../../../../../chips/models/chips-item.model'; import { ChipsItem } from '../../../../../chips/models/chips-item.model';
import { hasOnlyEmptyProperties } from '../../../../../object.util'; import { hasOnlyEmptyProperties } from '../../../../../object.util';
import { VocabularyFindOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-find-options.model';
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { environment } from '../../../../../../../environments/environment'; import { environment } from '../../../../../../../environments/environment';
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators'; import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
import { VocabularyEntryDetail } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; import { VocabularyEntryDetail } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
/**
* Component representing a group input field
*/
@Component({ @Component({
selector: 'ds-dynamic-relation-group', selector: 'ds-dynamic-relation-group',
styleUrls: ['./dynamic-relation-group.component.scss'], styleUrls: ['./dynamic-relation-group.component.scss'],

View File

@@ -1,5 +1,5 @@
<div #sdRef="ngbDropdown" ngbDropdown class="input-group w-100"> <div #sdRef="ngbDropdown" ngbDropdown class="w-100">
<input class="form-control" <input ngbDropdownToggle class="form-control custom-select"
[attr.autoComplete]="model.autoComplete" [attr.autoComplete]="model.autoComplete"
[class.is-invalid]="showErrorMessages" [class.is-invalid]="showErrorMessages"
[dynamicId]="bindId && model.id" [dynamicId]="bindId && model.id"
@@ -11,12 +11,6 @@
(click)="$event.stopPropagation(); openDropdown(sdRef);" (click)="$event.stopPropagation(); openDropdown(sdRef);"
(focus)="onFocus($event)" (focus)="onFocus($event)"
(keypress)="$event.preventDefault()"> (keypress)="$event.preventDefault()">
<button aria-describedby="collectionControlsMenuLabel"
class="ds-form-input-btn btn btn-outline-primary"
id="scrollableDropdownMenuButton_{{model.id}}"
ngbDropdownToggle
[disabled]="model.readOnly"
(click)="onToggle(sdRef); $event.stopPropagation();"></button>
<div ngbDropdownMenu <div ngbDropdownMenu
class="dropdown-menu scrollable-dropdown-menu w-100" class="dropdown-menu scrollable-dropdown-menu w-100"

View File

@@ -126,7 +126,7 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
}); });
it('should display dropdown menu entries', () => { it('should display dropdown menu entries', () => {
const de = scrollableDropdownFixture.debugElement.query(By.css('button.ds-form-input-btn')); const de = scrollableDropdownFixture.debugElement.query(By.css('input.custom-select'));
const btnEl = de.nativeElement; const btnEl = de.nativeElement;
const deMenu = scrollableDropdownFixture.debugElement.query(By.css('div.scrollable-dropdown-menu')); const deMenu = scrollableDropdownFixture.debugElement.query(By.css('div.scrollable-dropdown-menu'));
@@ -153,7 +153,7 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
it('should select a results entry properly', fakeAsync(() => { it('should select a results entry properly', fakeAsync(() => {
const selectedValue = Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 }); const selectedValue = Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 });
let de: any = scrollableDropdownFixture.debugElement.query(By.css('button.ds-form-input-btn')); let de: any = scrollableDropdownFixture.debugElement.query(By.css('input.custom-select'));
let btnEl = de.nativeElement; let btnEl = de.nativeElement;
btnEl.click(); btnEl.click();

View File

@@ -2,29 +2,29 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } fro
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { catchError, distinctUntilChanged, tap } from 'rxjs/operators'; import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import { import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
import { DynamicScrollableDropdownModel } from './dynamic-scrollable-dropdown.model'; import { DynamicScrollableDropdownModel } from './dynamic-scrollable-dropdown.model';
import { PageInfo } from '../../../../../../core/shared/page-info.model'; import { PageInfo } from '../../../../../../core/shared/page-info.model';
import { isNull, isUndefined } from '../../../../../empty.util'; import { isEmpty } from '../../../../../empty.util';
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
import { VocabularyFindOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-find-options.model';
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators'; import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
/**
* Component representing a dropdown input field
*/
@Component({ @Component({
selector: 'ds-dynamic-scrollable-dropdown', selector: 'ds-dynamic-scrollable-dropdown',
styleUrls: ['./dynamic-scrollable-dropdown.component.scss'], styleUrls: ['./dynamic-scrollable-dropdown.component.scss'],
templateUrl: './dynamic-scrollable-dropdown.component.html' templateUrl: './dynamic-scrollable-dropdown.component.html'
}) })
export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComponent implements OnInit { export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicScrollableDropdownModel; @Input() model: DynamicScrollableDropdownModel;
@@ -38,25 +38,20 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp
public pageInfo: PageInfo; public pageInfo: PageInfo;
public optionsList: any; public optionsList: any;
protected searchOptions: VocabularyFindOptions; constructor(protected vocabularyService: VocabularyService,
protected cdr: ChangeDetectorRef,
constructor(private vocabularyService: VocabularyService,
private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService, protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService protected validationService: DynamicFormValidationService
) { ) {
super(layoutService, validationService); super(vocabularyService, layoutService, validationService);
} }
/**
* Initialize the component, setting up the init form value
*/
ngOnInit() { ngOnInit() {
this.searchOptions = new VocabularyFindOptions( this.updatePageInfo(this.model.maxOptions, 1)
this.model.vocabularyOptions.scope, this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe(
this.model.vocabularyOptions.name,
this.model.vocabularyOptions.metadata,
'',
this.model.maxOptions,
1);
this.vocabularyService.getVocabularyEntries(this.searchOptions).pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
catchError(() => observableOf(new PaginatedList( catchError(() => observableOf(new PaginatedList(
new PageInfo(), new PageInfo(),
@@ -66,9 +61,15 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp
.subscribe((list: PaginatedList<VocabularyEntry>) => { .subscribe((list: PaginatedList<VocabularyEntry>) => {
this.optionsList = list.page; this.optionsList = list.page;
if (this.model.value) { if (this.model.value) {
this.setCurrentValue(this.model.value); this.setCurrentValue(this.model.value, true);
} }
this.pageInfo = list.pageInfo;
this.updatePageInfo(
list.pageInfo.elementsPerPage,
list.pageInfo.currentPage,
list.pageInfo.totalElements,
list.pageInfo.totalPages
);
this.cdr.detectChanges(); this.cdr.detectChanges();
}); });
@@ -76,22 +77,37 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp
.subscribe((value) => { .subscribe((value) => {
this.setCurrentValue(value); this.setCurrentValue(value);
}); });
} }
/**
* Converts an item from the result list to a `string` to display in the `<input>` field.
*/
inputFormatter = (x: VocabularyEntry): string => x.display || x.value; inputFormatter = (x: VocabularyEntry): string => x.display || x.value;
/**
* Opens dropdown menu
* @param sdRef The reference of the NgbDropdown.
*/
openDropdown(sdRef: NgbDropdown) { openDropdown(sdRef: NgbDropdown) {
if (!this.model.readOnly) { if (!this.model.readOnly) {
this.group.markAsUntouched();
sdRef.open(); sdRef.open();
} }
} }
/**
* Loads any new entries
*/
onScroll() { onScroll() {
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) { if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
this.loading = true; this.loading = true;
this.searchOptions.currentPage++; this.updatePageInfo(
this.vocabularyService.getVocabularyEntries(this.searchOptions).pipe( this.pageInfo.elementsPerPage,
this.pageInfo.currentPage + 1,
this.pageInfo.totalElements,
this.pageInfo.totalPages
);
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
catchError(() => observableOf(new PaginatedList( catchError(() => observableOf(new PaginatedList(
new PageInfo(), new PageInfo(),
@@ -101,49 +117,50 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp
tap(() => this.loading = false)) tap(() => this.loading = false))
.subscribe((list: PaginatedList<VocabularyEntry>) => { .subscribe((list: PaginatedList<VocabularyEntry>) => {
this.optionsList = this.optionsList.concat(list.page); this.optionsList = this.optionsList.concat(list.page);
this.pageInfo = list.pageInfo; this.updatePageInfo(
list.pageInfo.elementsPerPage,
list.pageInfo.currentPage,
list.pageInfo.totalElements,
list.pageInfo.totalPages
);
this.cdr.detectChanges(); this.cdr.detectChanges();
}) })
} }
} }
onBlur(event: Event) { /**
this.blur.emit(event); * Emits a change event and set the current value with the given value.
} * @param event The value to emit.
*/
onFocus(event) {
this.focus.emit(event);
}
onSelect(event) { onSelect(event) {
this.group.markAsDirty(); this.group.markAsDirty();
this.model.valueUpdates.next(event); this.dispatchUpdate(event);
this.change.emit(event);
this.setCurrentValue(event); this.setCurrentValue(event);
} }
onToggle(sdRef: NgbDropdown) { /**
if (sdRef.isOpen()) { * Sets the current value with the given value.
this.focus.emit(event); * @param value The value to set.
} else { * @param init Representing if is init value or not.
this.blur.emit(event); */
} setCurrentValue(value: any, init = false): void {
} let result: Observable<string>;
setCurrentValue(value): void { if (init) {
let result: string; result = this.getInitValueFromModel().pipe(
if (isUndefined(value) || isNull(value)) { map((value: FormFieldMetadataValueObject) => value.display)
result = ''; );
} else if (typeof value === 'string') {
result = value;
} else { } else {
for (const item of this.optionsList) { if (isEmpty(value)) {
if (value.value === (item as any).value) { result = observableOf('');
result = this.inputFormatter(item); } else if (typeof value === 'string') {
break; result = observableOf(value);
} } else {
result = observableOf(value.display)
} }
} }
this.currentValue = observableOf(result);
this.currentValue = result;
} }
} }

View File

@@ -142,15 +142,13 @@ describe('DsDynamicTagComponent test suite', () => {
it('should init component properly', () => { it('should init component properly', () => {
chips = new Chips([], 'display'); chips = new Chips([], 'display');
expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems()); expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
expect(tagComp.searchOptions).toBeDefined();
}); });
it('should search when 3+ characters typed', fakeAsync(() => { it('should search when 3+ characters typed', fakeAsync(() => {
spyOn((tagComp as any).vocabularyService, 'getVocabularyEntries').and.callThrough(); spyOn((tagComp as any).vocabularyService, 'getVocabularyEntriesByValue').and.callThrough();
tagComp.search(observableOf('test')).subscribe(() => { tagComp.search(observableOf('test')).subscribe(() => {
expect((tagComp as any).vocabularyService.getVocabularyEntries).toHaveBeenCalled(); expect((tagComp as any).vocabularyService.getVocabularyEntriesByValue).toHaveBeenCalled();
}); });
})); }));
@@ -232,7 +230,6 @@ describe('DsDynamicTagComponent test suite', () => {
it('should init component properly', () => { it('should init component properly', () => {
chips = new Chips(modelValue, 'display'); chips = new Chips(modelValue, 'display');
expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems()); expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
expect(tagComp.searchOptions).toBeDefined();
}); });
}); });
@@ -259,7 +256,6 @@ describe('DsDynamicTagComponent test suite', () => {
it('should init component properly', () => { it('should init component properly', () => {
chips = new Chips([], 'display'); chips = new Chips([], 'display');
expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems()); expect(tagComp.chips.getChipsItems()).toEqual(chips.getChipsItems());
expect(tagComp.searchOptions).not.toBeDefined();
}); });
it('should add an item on ENTER or key press is \',\' or \';\'', fakeAsync(() => { it('should add an item on ENTER or key press is \',\' or \';\'', fakeAsync(() => {

View File

@@ -1,11 +1,7 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, map, merge, switchMap, tap } from 'rxjs/operators'; import { catchError, debounceTime, distinctUntilChanged, map, merge, switchMap, tap } from 'rxjs/operators';
import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
@@ -13,7 +9,6 @@ import { isEqual } from 'lodash';
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
import { DynamicTagModel } from './dynamic-tag.model'; import { DynamicTagModel } from './dynamic-tag.model';
import { VocabularyFindOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-find-options.model';
import { Chips } from '../../../../../chips/models/chips.model'; import { Chips } from '../../../../../chips/models/chips.model';
import { hasValue, isNotEmpty } from '../../../../../empty.util'; import { hasValue, isNotEmpty } from '../../../../../empty.util';
import { environment } from '../../../../../../../environments/environment'; import { environment } from '../../../../../../../environments/environment';
@@ -21,13 +16,18 @@ import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/share
import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
import { PageInfo } from '../../../../../../core/shared/page-info.model'; import { PageInfo } from '../../../../../../core/shared/page-info.model';
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
/**
* Component representing a tag input field
*/
@Component({ @Component({
selector: 'ds-dynamic-tag', selector: 'ds-dynamic-tag',
styleUrls: ['./dynamic-tag.component.scss'], styleUrls: ['./dynamic-tag.component.scss'],
templateUrl: './dynamic-tag.component.html' templateUrl: './dynamic-tag.component.html'
}) })
export class DsDynamicTagComponent extends DynamicFormControlComponent implements OnInit { export class DsDynamicTagComponent extends DsDynamicVocabularyComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicTagModel; @Input() model: DynamicTagModel;
@@ -42,21 +42,28 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
hasAuthority: boolean; hasAuthority: boolean;
searching = false; searching = false;
searchOptions: VocabularyFindOptions;
searchFailed = false; searchFailed = false;
hideSearchingWhenUnsubscribed = new Observable(() => () => this.changeSearchingStatus(false)); hideSearchingWhenUnsubscribed = new Observable(() => () => this.changeSearchingStatus(false));
currentValue: any; currentValue: any;
public pageInfo: PageInfo;
constructor(private vocabularyService: VocabularyService, constructor(protected vocabularyService: VocabularyService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService, protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService protected validationService: DynamicFormValidationService
) { ) {
super(layoutService, validationService); super(vocabularyService, layoutService, validationService);
} }
/**
* Converts an item from the result list to a `string` to display in the `<input>` field.
*/
formatter = (x: { display: string }) => x.display; formatter = (x: { display: string }) => x.display;
/**
* Converts a stream of text values from the `<input>` element to the stream of the array of items
* to display in the typeahead popup.
*/
search = (text$: Observable<string>) => search = (text$: Observable<string>) =>
text$.pipe( text$.pipe(
debounceTime(300), debounceTime(300),
@@ -66,8 +73,7 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
if (term === '' || term.length < this.model.minChars) { if (term === '' || term.length < this.model.minChars) {
return observableOf({ list: [] }); return observableOf({ list: [] });
} else { } else {
this.searchOptions.query = term; return this.vocabularyService.getVocabularyEntriesByValue(term, false, this.model.vocabularyOptions, new PageInfo()).pipe(
return this.vocabularyService.getVocabularyEntries(this.searchOptions).pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
tap(() => this.searchFailed = false), tap(() => this.searchFailed = false),
catchError(() => { catchError(() => {
@@ -83,16 +89,12 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
tap(() => this.changeSearchingStatus(false)), tap(() => this.changeSearchingStatus(false)),
merge(this.hideSearchingWhenUnsubscribed)); merge(this.hideSearchingWhenUnsubscribed));
/**
* Initialize the component, setting up the init form value
*/
ngOnInit() { ngOnInit() {
this.hasAuthority = this.model.vocabularyOptions && hasValue(this.model.vocabularyOptions.name); this.hasAuthority = this.model.vocabularyOptions && hasValue(this.model.vocabularyOptions.name);
if (this.hasAuthority) {
this.searchOptions = new VocabularyFindOptions(
this.model.vocabularyOptions.scope,
this.model.vocabularyOptions.name,
this.model.vocabularyOptions.metadata);
}
this.chips = new Chips( this.chips = new Chips(
this.model.value, this.model.value,
'display', 'display',
@@ -104,17 +106,24 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
const items = this.chips.getChipsItems(); const items = this.chips.getChipsItems();
// Does not emit change if model value is equal to the current value // Does not emit change if model value is equal to the current value
if (!isEqual(items, this.model.value)) { if (!isEqual(items, this.model.value)) {
this.model.valueUpdates.next(items); this.dispatchUpdate(items);
this.change.emit(event);
} }
}); });
} }
/**
* Changes the searching status
* @param status
*/
changeSearchingStatus(status: boolean) { changeSearchingStatus(status: boolean) {
this.searching = status; this.searching = status;
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
/**
* Mark form group as dirty on input
* @param event
*/
onInput(event) { onInput(event) {
if (event.data) { if (event.data) {
this.group.markAsDirty(); this.group.markAsDirty();
@@ -122,6 +131,10 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
/**
* Emits a blur event containing a given value and add all tags to chips.
* @param event The value to emit.
*/
onBlur(event: Event) { onBlur(event: Event) {
if (isNotEmpty(this.currentValue) && !this.instance.isPopupOpen()) { if (isNotEmpty(this.currentValue) && !this.instance.isPopupOpen()) {
this.addTagsToChips(); this.addTagsToChips();
@@ -129,10 +142,10 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
this.blur.emit(event); this.blur.emit(event);
} }
onFocus(event) { /**
this.focus.emit(event); * Updates model value with the selected value and add a new tag to chips.
} * @param event The value to set.
*/
onSelectItem(event: NgbTypeaheadSelectItemEvent) { onSelectItem(event: NgbTypeaheadSelectItemEvent) {
this.chips.add(event.item); this.chips.add(event.item);
// this.group.controls[this.model.id].setValue(this.model.value); // this.group.controls[this.model.id].setValue(this.model.value);
@@ -140,25 +153,34 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
setTimeout(() => { setTimeout(() => {
// Reset the input text after x ms, mandatory or the formatter overwrite it // Reset the input text after x ms, mandatory or the formatter overwrite it
this.currentValue = null; this.setCurrentValue(null);
this.cdr.detectChanges(); this.cdr.detectChanges();
}, 50); }, 50);
} }
updateModel(event) { updateModel(event) {
this.model.valueUpdates.next(this.chips.getChipsItems()); /* this.model.valueUpdates.next(this.chips.getChipsItems());
this.change.emit(event); this.change.emit(event);*/
this.dispatchUpdate(this.chips.getChipsItems());
} }
/**
* Add a new tag with typed text when typing 'Enter' or ',' or ';'
* @param event the keyUp event
*/
onKeyUp(event) { onKeyUp(event) {
if (event.keyCode === 13 || event.keyCode === 188) { if (event.keyCode === 13 || event.keyCode === 188) {
event.preventDefault(); event.preventDefault();
// Key: Enter or ',' or ';' // Key: 'Enter' or ',' or ';'
this.addTagsToChips(); this.addTagsToChips();
event.stopPropagation(); event.stopPropagation();
} }
} }
/**
* Prevent propagation of a key event in case of return key is pressed
* @param event the key event
*/
preventEventsPropagation(event) { preventEventsPropagation(event) {
event.stopPropagation(); event.stopPropagation();
if (event.keyCode === 13) { if (event.keyCode === 13) {
@@ -166,6 +188,15 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
} }
} }
/**
* Sets the current value with the given value.
* @param value The value to set.
* @param init Representing if is init value or not.
*/
public setCurrentValue(value: any, init = false) {
this.currentValue = value;
}
private addTagsToChips() { private addTagsToChips() {
if (hasValue(this.currentValue) && (!this.hasAuthority || !this.model.vocabularyOptions.closed)) { if (hasValue(this.currentValue) && (!this.hasAuthority || !this.model.vocabularyOptions.closed)) {
let res: string[] = []; let res: string[] = [];
@@ -188,7 +219,7 @@ export class DsDynamicTagComponent extends DynamicFormControlComponent implement
// this.currentValue = ''; // this.currentValue = '';
setTimeout(() => { setTimeout(() => {
// Reset the input text after x ms, mandatory or the formatter overwrite it // Reset the input text after x ms, mandatory or the formatter overwrite it
this.currentValue = null; this.setCurrentValue(null);
this.cdr.detectChanges(); this.cdr.detectChanges();
}, 50); }, 50);
this.updateModel(event); this.updateModel(event);

View File

@@ -19,6 +19,7 @@ import { FormFieldMetadataValueObject } from '../../../models/form-field-metadat
import { createTestComponent } from '../../../../../testing/utils.test'; import { createTestComponent } from '../../../../../testing/utils.test';
import { AuthorityConfidenceStateDirective } from '../../../../../authority-confidence/authority-confidence-state.directive'; import { AuthorityConfidenceStateDirective } from '../../../../../authority-confidence/authority-confidence-state.directive';
import { ObjNgFor } from '../../../../../utils/object-ngfor.pipe'; import { ObjNgFor } from '../../../../../utils/object-ngfor.pipe';
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
export let TYPEAHEAD_TEST_GROUP; export let TYPEAHEAD_TEST_GROUP;
@@ -134,14 +135,14 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
it('should search when 3+ characters typed', fakeAsync(() => { it('should search when 3+ characters typed', fakeAsync(() => {
spyOn((typeaheadComp as any).vocabularyService, 'getVocabularyEntries').and.callThrough(); spyOn((typeaheadComp as any).vocabularyService, 'getVocabularyEntriesByValue').and.callThrough();
typeaheadComp.search(observableOf('test')).subscribe(); typeaheadComp.search(observableOf('test')).subscribe();
tick(300); tick(300);
typeaheadFixture.detectChanges(); typeaheadFixture.detectChanges();
expect((typeaheadComp as any).vocabularyService.getVocabularyEntries).toHaveBeenCalled(); expect((typeaheadComp as any).vocabularyService.getVocabularyEntriesByValue).toHaveBeenCalled();
})); }));
it('should set model.value on input type when VocabularyOptions.closed is false', () => { it('should set model.value on input type when VocabularyOptions.closed is false', () => {
@@ -229,13 +230,20 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
}); });
describe('and init model value is not empty', () => { describe('when init model value is not empty', () => {
beforeEach(() => { beforeEach(() => {
typeaheadFixture = TestBed.createComponent(DsDynamicTypeaheadComponent); typeaheadFixture = TestBed.createComponent(DsDynamicTypeaheadComponent);
typeaheadComp = typeaheadFixture.componentInstance; // FormComponent test instance typeaheadComp = typeaheadFixture.componentInstance; // FormComponent test instance
typeaheadComp.group = TYPEAHEAD_TEST_GROUP; typeaheadComp.group = TYPEAHEAD_TEST_GROUP;
typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG); typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
(typeaheadComp.model as any).value = new FormFieldMetadataValueObject('test', null, 'test001'); const entry = observableOf(Object.assign(new VocabularyEntry(), {
authority: null,
value: 'test',
display: 'testDisplay'
}));
spyOn((typeaheadComp as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
spyOn((typeaheadComp as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
(typeaheadComp.model as any).value = new FormFieldMetadataValueObject('test', null, null, 'testDisplay');
typeaheadFixture.detectChanges(); typeaheadFixture.detectChanges();
}); });
@@ -244,9 +252,11 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
typeaheadComp = null; typeaheadComp = null;
}); });
it('should init component properly', () => { it('should init component properly', fakeAsync(() => {
expect(typeaheadComp.currentValue).toEqual(new FormFieldMetadataValueObject('test', null, 'test001')); tick();
}); expect(typeaheadComp.currentValue).toEqual(new FormFieldMetadataValueObject('test', null, null, 'testDisplay'));
expect((typeaheadComp as any).vocabularyService.getVocabularyEntryByValue).toHaveBeenCalled();
}));
it('should emit change Event onChange and currentValue is empty', () => { it('should emit change Event onChange and currentValue is empty', () => {
typeaheadComp.currentValue = null; typeaheadComp.currentValue = null;
@@ -257,6 +267,42 @@ describe('DsDynamicTypeaheadComponent test suite', () => {
}); });
}); });
describe('when init model value is not empty and has authority', () => {
beforeEach(() => {
typeaheadFixture = TestBed.createComponent(DsDynamicTypeaheadComponent);
typeaheadComp = typeaheadFixture.componentInstance; // FormComponent test instance
typeaheadComp.group = TYPEAHEAD_TEST_GROUP;
typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG);
const entry = observableOf(Object.assign(new VocabularyEntry(), {
authority: 'test001',
value: 'test001',
display: 'test'
}));
spyOn((typeaheadComp as any).vocabularyService, 'getVocabularyEntryByValue').and.returnValue(entry);
spyOn((typeaheadComp as any).vocabularyService, 'getVocabularyEntryByID').and.returnValue(entry);
(typeaheadComp.model as any).value = new FormFieldMetadataValueObject('test', null, 'test001');
typeaheadFixture.detectChanges();
});
afterEach(() => {
typeaheadFixture.destroy();
typeaheadComp = null;
});
it('should init component properly', fakeAsync(() => {
tick();
expect(typeaheadComp.currentValue).toEqual(new FormFieldMetadataValueObject('test001', null, 'test001', 'test'));
expect((typeaheadComp as any).vocabularyService.getVocabularyEntryByID).toHaveBeenCalled();
}));
it('should emit change Event onChange and currentValue is empty', () => {
typeaheadComp.currentValue = null;
spyOn(typeaheadComp.change, 'emit');
typeaheadComp.onChange(new Event('change'));
expect(typeaheadComp.change.emit).toHaveBeenCalled();
expect(typeaheadComp.model.value).toBeNull();
});
});
}); });
}); });

View File

@@ -1,18 +1,13 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { catchError, debounceTime, distinctUntilChanged, filter, map, merge, switchMap, tap } from 'rxjs/operators'; import { catchError, debounceTime, distinctUntilChanged, filter, map, merge, switchMap, tap } from 'rxjs/operators';
import { Observable, of as observableOf, Subject } from 'rxjs'; import { Observable, of as observableOf, Subject } from 'rxjs';
import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
import { DynamicTypeaheadModel } from './dynamic-typeahead.model'; import { DynamicTypeaheadModel } from './dynamic-typeahead.model';
import { VocabularyFindOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-find-options.model';
import { isEmpty, isNotEmpty, isNotNull } from '../../../../../empty.util'; import { isEmpty, isNotEmpty, isNotNull } from '../../../../../empty.util';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
import { ConfidenceType } from '../../../../../../core/shared/confidence-type'; import { ConfidenceType } from '../../../../../../core/shared/confidence-type';
@@ -20,13 +15,14 @@ import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/share
import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
import { PageInfo } from '../../../../../../core/shared/page-info.model'; import { PageInfo } from '../../../../../../core/shared/page-info.model';
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
@Component({ @Component({
selector: 'ds-dynamic-typeahead', selector: 'ds-dynamic-typeahead',
styleUrls: ['./dynamic-typeahead.component.scss'], styleUrls: ['./dynamic-typeahead.component.scss'],
templateUrl: './dynamic-typeahead.component.html' templateUrl: './dynamic-typeahead.component.html'
}) })
export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent implements OnInit { export class DsDynamicTypeaheadComponent extends DsDynamicVocabularyComponent implements OnInit {
@Input() bindId = true; @Input() bindId = true;
@Input() group: FormGroup; @Input() group: FormGroup;
@Input() model: DynamicTypeaheadModel; @Input() model: DynamicTypeaheadModel;
@@ -37,26 +33,33 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp
@ViewChild('instance', { static: false }) instance: NgbTypeahead; @ViewChild('instance', { static: false }) instance: NgbTypeahead;
pageInfo: PageInfo;
searching = false; searching = false;
searchOptions: VocabularyFindOptions;
searchFailed = false; searchFailed = false;
hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.changeSearchingStatus(false)); hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.changeSearchingStatus(false));
click$ = new Subject<string>(); click$ = new Subject<string>();
currentValue: any; currentValue: any;
inputValue: any; inputValue: any;
constructor(private vocabularyService: VocabularyService, constructor(protected vocabularyService: VocabularyService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
protected layoutService: DynamicFormLayoutService, protected layoutService: DynamicFormLayoutService,
protected validationService: DynamicFormValidationService protected validationService: DynamicFormValidationService
) { ) {
super(layoutService, validationService); super(vocabularyService, layoutService, validationService);
} }
/**
* Converts an item from the result list to a `string` to display in the `<input>` field.
*/
formatter = (x: { display: string }) => { formatter = (x: { display: string }) => {
return (typeof x === 'object') ? x.display : x return (typeof x === 'object') ? x.display : x
}; };
/**
* Converts a stream of text values from the `<input>` element to the stream of the array of items
* to display in the typeahead popup.
*/
search = (text$: Observable<string>) => { search = (text$: Observable<string>) => {
return text$.pipe( return text$.pipe(
merge(this.click$), merge(this.click$),
@@ -67,8 +70,11 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp
if (term === '' || term.length < this.model.minChars) { if (term === '' || term.length < this.model.minChars) {
return observableOf({ list: [] }); return observableOf({ list: [] });
} else { } else {
this.searchOptions.query = term; return this.vocabularyService.getVocabularyEntriesByValue(
return this.vocabularyService.getVocabularyEntries(this.searchOptions).pipe( term,
false,
this.model.vocabularyOptions,
this.pageInfo).pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
tap(() => this.searchFailed = false), tap(() => this.searchFailed = false),
catchError(() => { catchError(() => {
@@ -86,36 +92,49 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp
) )
}; };
/**
* Initialize the component, setting up the init form value
*/
ngOnInit() { ngOnInit() {
this.currentValue = this.model.value; if (this.model.value) {
this.searchOptions = new VocabularyFindOptions( this.setCurrentValue(this.model.value, true);
this.model.vocabularyOptions.scope, }
this.model.vocabularyOptions.name,
this.model.vocabularyOptions.metadata);
this.group.get(this.model.id).valueChanges.pipe( this.group.get(this.model.id).valueChanges.pipe(
filter((value) => this.currentValue !== value)) filter((value) => this.currentValue !== value))
.subscribe((value) => { .subscribe((value) => {
this.currentValue = value; this.setCurrentValue(this.model.value);
}); });
} }
/**
* Changes the searching status
* @param status
*/
changeSearchingStatus(status: boolean) { changeSearchingStatus(status: boolean) {
this.searching = status; this.searching = status;
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
/**
* Update the input value with a FormFieldMetadataValueObject
* @param event
*/
onInput(event) { onInput(event) {
if (!this.model.vocabularyOptions.closed && isNotEmpty(event.target.value)) { if (!this.model.vocabularyOptions.closed && isNotEmpty(event.target.value)) {
this.inputValue = new FormFieldMetadataValueObject(event.target.value); this.inputValue = new FormFieldMetadataValueObject(event.target.value);
} }
} }
/**
* Emits a blur event containing a given value.
* @param event The value to emit.
*/
onBlur(event: Event) { onBlur(event: Event) {
if (!this.instance.isPopupOpen()) { if (!this.instance.isPopupOpen()) {
if (!this.model.vocabularyOptions.closed && isNotEmpty(this.inputValue)) { if (!this.model.vocabularyOptions.closed && isNotEmpty(this.inputValue)) {
if (isNotNull(this.inputValue) && this.model.value !== this.inputValue) { if (isNotNull(this.inputValue) && this.model.value !== this.inputValue) {
this.model.valueUpdates.next(this.inputValue); this.dispatchUpdate(this.inputValue);
this.change.emit(this.inputValue);
} }
this.inputValue = null; this.inputValue = null;
} }
@@ -129,29 +148,60 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp
} }
} }
/**
* Updates model value with the current value
* @param event The change event.
*/
onChange(event: Event) { onChange(event: Event) {
event.stopPropagation(); event.stopPropagation();
if (isEmpty(this.currentValue)) { if (isEmpty(this.currentValue)) {
this.model.valueUpdates.next(null); this.dispatchUpdate(null);
this.change.emit(null);
} }
} }
onFocus(event) { /**
this.focus.emit(event); * Updates current value and model value with the selected value.
} * @param event The value to set.
*/
onSelectItem(event: NgbTypeaheadSelectItemEvent) { onSelectItem(event: NgbTypeaheadSelectItemEvent) {
this.inputValue = null; this.inputValue = null;
this.currentValue = event.item; this.setCurrentValue(event.item);
this.model.valueUpdates.next(event.item); this.dispatchUpdate(event.item);
this.change.emit(event.item);
} }
/**
* Callback functions for whenClickOnConfidenceNotAccepted event
*/
public whenClickOnConfidenceNotAccepted(confidence: ConfidenceType) { public whenClickOnConfidenceNotAccepted(confidence: ConfidenceType) {
if (!this.model.readOnly) { if (!this.model.readOnly) {
this.click$.next(this.formatter(this.currentValue)); this.click$.next(this.formatter(this.currentValue));
} }
} }
/**
* Sets the current value with the given value.
* @param value The value to set.
* @param init Representing if is init value or not.
*/
setCurrentValue(value: any, init = false): void {
let result: string;
if (init) {
this.getInitValueFromModel()
.subscribe((value: FormFieldMetadataValueObject) => {
this.currentValue = value;
});
} else {
if (isEmpty(value)) {
result = '';
} else if (typeof value === 'string') {
result = value;
} else {
result = value.display;
}
this.currentValue = result;
}
}
} }

View File

@@ -1,12 +1,10 @@
import { FieldParser } from './field-parser'; import { FieldParser } from './field-parser';
import { isNotEmpty } from '../../../empty.util'; import { isNotEmpty } from '../../../empty.util';
import { VocabularyFindOptions } from '../../../../core/submission/vocabularies/models/vocabulary-find-options.model';
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
import { DynamicListCheckboxGroupModel } from '../ds-dynamic-form-ui/models/list/dynamic-list-checkbox-group.model'; import { DynamicListCheckboxGroupModel } from '../ds-dynamic-form-ui/models/list/dynamic-list-checkbox-group.model';
import { DynamicListRadioGroupModel } from '../ds-dynamic-form-ui/models/list/dynamic-list-radio-group.model'; import { DynamicListRadioGroupModel } from '../ds-dynamic-form-ui/models/list/dynamic-list-radio-group.model';
export class ListFieldParser extends FieldParser { export class ListFieldParser extends FieldParser {
searchOptions: VocabularyFindOptions;
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any { public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
const listModelConfig = this.initModel(null, label); const listModelConfig = this.initModel(null, label);

View File

@@ -1,22 +1,13 @@
import { Injectable, Injector } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import {
DYNAMIC_FORM_CONTROL_TYPE_ARRAY, import { DYNAMIC_FORM_CONTROL_TYPE_ARRAY, DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core';
DynamicFormGroupModelConfig
} from '@ng-dynamic-forms/core';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import { VocabularyFindOptions } from '../../../../core/submission/vocabularies/models/vocabulary-find-options.model';
import { isEmpty } from '../../../empty.util'; import { isEmpty } from '../../../empty.util';
import { DynamicRowGroupModel } from '../ds-dynamic-form-ui/models/ds-dynamic-row-group-model'; import { DynamicRowGroupModel } from '../ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from '../ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model'; import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from '../ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
import { FormFieldModel } from '../models/form-field.model'; import { FormFieldModel } from '../models/form-field.model';
import { import { CONFIG_DATA, FieldParser, INIT_FORM_VALUES, PARSER_OPTIONS, SUBMISSION_ID } from './field-parser';
CONFIG_DATA,
FieldParser,
INIT_FORM_VALUES,
PARSER_OPTIONS,
SUBMISSION_ID
} from './field-parser';
import { ParserFactory } from './parser-factory'; import { ParserFactory } from './parser-factory';
import { ParserOptions } from './parser-options'; import { ParserOptions } from './parser-options';
import { ParserType } from './parser-type'; import { ParserType } from './parser-type';
@@ -48,8 +39,6 @@ export class RowParser {
group: [], group: [],
}; };
const vocabularyOptions = new VocabularyFindOptions(scopeUUID);
const scopedFields: FormFieldModel[] = this.filterScopedFields(rowData.fields, submissionScope); const scopedFields: FormFieldModel[] = this.filterScopedFields(rowData.fields, submissionScope);
const layoutDefaultGridClass = ' col-sm-' + Math.trunc(12 / scopedFields.length); const layoutDefaultGridClass = ' col-sm-' + Math.trunc(12 / scopedFields.length);
@@ -58,7 +47,7 @@ export class RowParser {
const parserOptions: ParserOptions = { const parserOptions: ParserOptions = {
readOnly: readOnly, readOnly: readOnly,
submissionScope: submissionScope, submissionScope: submissionScope,
collectionUUID: vocabularyOptions.collection collectionUUID: scopeUUID
}; };
// Iterate over row's fields // Iterate over row's fields