mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
[CST-3088] Created abstract form component to handle vocabularies
This commit is contained in:
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
@@ -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">
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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'],
|
||||||
|
@@ -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"
|
||||||
|
@@ -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();
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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(() => {
|
||||||
|
@@ -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);
|
||||||
|
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user