From 1099910e8e710edeb830ef1bdc13644672307417 Mon Sep 17 00:00:00 2001 From: flaurens Date: Thu, 10 Dec 2020 17:19:58 +0100 Subject: [PATCH 01/14] feat(select-properties): check if the prop is required according to its cardinality --- .../select-properties.component.html | 6 ++- .../select-properties.component.scss | 4 ++ .../select-properties.component.ts | 38 ++++++++++++++++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html index b7e617a98f..1e87acce9f 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html @@ -1,6 +1,6 @@
-
+

{{prop.label}} +

+ * +

+
diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss index 53a0e43cd7..5d1a47dd11 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss @@ -45,6 +45,10 @@ .label-info { cursor: help; } + + .propIsRequired { + color: red; + } } .property-value { diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts index 788df07b1c..f4d27097a4 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts @@ -1,6 +1,6 @@ -import { Component, Input, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { AfterViewInit, Component, Input, OnInit, QueryList, ViewChildren } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { CardinalityUtil, ReadResource, ResourceClassAndPropertyDefinitions, ResourceClassDefinition, ResourcePropertyDefinition } from '@dasch-swiss/dsp-js'; +import { Cardinality, CardinalityUtil, IHasProperty, ReadResource, ResourceClassAndPropertyDefinitions, ResourceClassDefinition, ResourcePropertyDefinition } from '@dasch-swiss/dsp-js'; import { ValueService } from '@dasch-swiss/dsp-ui'; import { SwitchPropertiesComponent } from './switch-properties/switch-properties.component'; @@ -17,7 +17,7 @@ export class SelectPropertiesComponent implements OnInit { @ViewChildren('switchProp') switchPropertiesComponent: QueryList; - @Input() propertiesAsArray: Array; + @Input() properties: ResourcePropertyDefinition[]; @Input() ontologyInfo: ResourceClassAndPropertyDefinitions; @@ -33,11 +33,15 @@ export class SelectPropertiesComponent implements OnInit { addButtonIsVisible: boolean; + isRequiredProp: boolean; + constructor(private _valueService: ValueService) { } ngOnInit() { - if (this.propertiesAsArray) { - for (const prop of this.propertiesAsArray) { + // console.log('resourceClass', this.resourceClass.propertiesList); + // console.log('properties', this.properties); + if (this.properties) { + for (const prop of this.properties) { if (prop) { if (prop.objectType === 'http://api.knora.org/ontology/knora-api/v2#TextValue') { prop.objectType = this._valueService.getTextValueClass(prop); @@ -49,6 +53,10 @@ export class SelectPropertiesComponent implements OnInit { // each property will also have a filtered array to be used when deleting a value. // see the deleteValue method below for more info this.propertyValuesKeyValuePair[prop.id + '-filtered'] = [0]; + + // check the cardinality to know if the prop is required or not + this.isPropRequired(prop.id); + this.propertyValuesKeyValuePair[prop.id + '-cardinality'] = [this.isRequiredProp ? 1 : 0]; } } } @@ -56,6 +64,7 @@ export class SelectPropertiesComponent implements OnInit { this.parentResource.entityInfo = this.ontologyInfo; } + /** * Given a resource property, check if an add button should be displayed under the property values * @@ -69,6 +78,25 @@ export class SelectPropertiesComponent implements OnInit { ); } + isPropRequired(propId: string): void { + if (this.resourceClass !== undefined && propId) { + this.resourceClass.propertiesList.filter( + (card: IHasProperty) => { + if (card.propertyIndex === propId) { + // cardinality 1 or 1-N + if (card.cardinality === Cardinality._1 || card.cardinality === Cardinality._1_n) { + this.isRequiredProp = true; + } else { + this.isRequiredProp = false; + } + } else { + console.log('false'); + } + } + ); + } + } + /** * Called from the template when the user clicks on the add button */ From 4c643d3c4f1dbee6e1b1e571f16627dfdb0067e7 Mon Sep 17 00:00:00 2001 From: flaurens Date: Fri, 11 Dec 2020 11:08:47 +0100 Subject: [PATCH 02/14] test(select-properties): fix test --- .../select-properties/select-properties.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts index 53473f8f6d..53b986f3b9 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts @@ -96,7 +96,7 @@ describe('SelectPropertiesComponent', () => { } // each property has two entries in the keyValuePair object - expect(propsArray.length).toEqual(18 * 2); + expect(propsArray.length).toEqual(18 * 3); }); describe('Add/Delete functionality', () => { From e365fe28708538917d0b20a0fd615c752b9915b7 Mon Sep 17 00:00:00 2001 From: flaurens Date: Fri, 18 Dec 2020 10:33:58 +0100 Subject: [PATCH 03/14] style(select-properties): fix the alignment label-asterisk --- .../select-properties.component.html | 11 ++++++----- .../select-properties.component.scss | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html index 1e87acce9f..958f1f5793 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html @@ -3,16 +3,17 @@
-

+

{{prop.label}} -

- * -

- + + + * +
diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss index 5d1a47dd11..d236932b33 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.scss @@ -40,6 +40,9 @@ .label { text-align: right; + display: block; + float: left; + width: 95%; } .label-info { @@ -48,6 +51,9 @@ .propIsRequired { color: red; + display: block; + float: right; + width: 5%; } } From a79b50d96a1bd10b09ed7b1752347cdfaa1b3624 Mon Sep 17 00:00:00 2001 From: flaurens Date: Fri, 18 Dec 2020 17:09:38 +0100 Subject: [PATCH 04/14] feat(switch-properties): add input valueRequiredValidator --- .../switch-properties.component.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html index e915b03a5c..9625284298 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html +++ b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html @@ -1,18 +1,18 @@ - - - - - - - - - - - + + + + + + + + + + - - +

Cannot match any value component for {{property.objectType}}

From cf2245f615945754f6828e915cbdad1eab4fc342 Mon Sep 17 00:00:00 2001 From: flaurens Date: Wed, 6 Jan 2021 14:09:14 +0100 Subject: [PATCH 05/14] fix(switch-properties): pass to the input valueRequiredValidator a variable instead of false --- .../select-properties.component.html | 3 ++- .../switch-properties.component.html | 26 +++++++++---------- .../switch-properties.component.ts | 15 +++++------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html index 958f1f5793..ec75ca9a63 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.html @@ -24,7 +24,8 @@ [property]="prop" [parentResource]="parentResource" [parentForm]="parentForm" - [formName]="prop.label + '_' + i"> + [formName]="prop.label + '_' + i" + [isRequiredProp]="propertyValuesKeyValuePair[prop.id + '-cardinality']">
diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html index 9625284298..4e841e88b3 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html +++ b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.html @@ -1,18 +1,18 @@ - - - - - - - - - - - + + + + + + + + + + - - +

Cannot match any value component for {{property.objectType}}

diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts index 2fbe9287e3..5307a00706 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { Constants, ReadResource, ResourcePropertyDefinition } from '@dasch-swiss/dsp-js'; import { BaseValueComponent } from 'src/app/base-value.component'; @@ -8,7 +8,7 @@ import { BaseValueComponent } from 'src/app/base-value.component'; templateUrl: './switch-properties.component.html', styleUrls: ['./switch-properties.component.scss'] }) -export class SwitchPropertiesComponent implements OnInit, AfterViewInit { +export class SwitchPropertiesComponent implements OnInit { @ViewChild('createVal') createValueComponent: BaseValueComponent; @@ -20,23 +20,20 @@ export class SwitchPropertiesComponent implements OnInit, AfterViewInit { @Input() formName: string; + @Input() isRequiredProp: boolean; + mode = 'create'; constants = Constants; constructor() { } ngOnInit(): void { - // console.log('prop', this.property); - } - - ngAfterViewInit() { - // console.log('createValueComponent', this.createValueComponent); - // this.saveNewValue(); + // convert from boolean (1/0) to boolean (true/false) + this.isRequiredProp = !!+this.isRequiredProp; } saveNewValue() { const createVal = this.createValueComponent.getNewValue(); - console.log('createVal', createVal); } } From b0499b825a56f9c4862e2bd45199ec028e9459a2 Mon Sep 17 00:00:00 2001 From: flaurens Date: Fri, 15 Jan 2021 11:58:49 +0100 Subject: [PATCH 06/14] feat(resource-instance-form): scroll to the first invalid form field --- src/app/app.module.ts | 4 ++ src/app/main/dialog/dialog.component.html | 2 +- ...control-scroll-container.directive.spec.ts | 8 +++ ...alid-control-scroll-container.directive.ts | 12 ++++ .../invalid-control-scroll.directive.spec.ts | 8 +++ .../invalid-control-scroll.directive.ts | 49 ++++++++++++++ .../resource-instance-form.component.html | 10 ++- .../resource-instance-form.component.ts | 67 ++++++++++--------- .../select-properties.component.ts | 2 - 9 files changed, 126 insertions(+), 36 deletions(-) create mode 100644 src/app/main/directive/invalid-control-scroll-container.directive.spec.ts create mode 100644 src/app/main/directive/invalid-control-scroll-container.directive.ts create mode 100644 src/app/main/directive/invalid-control-scroll.directive.spec.ts create mode 100644 src/app/main/directive/invalid-control-scroll.directive.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a8014b78ac..7c58929c4d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -72,6 +72,8 @@ import { ResultsComponent } from './workspace/results/results.component'; import { environment } from '../environments/environment'; import { ExternalLinksDirective } from './main/directive/external-links.directive'; +import { InvalidControlScrollContainerDirective } from './main/directive/invalid-control-scroll-container.directive'; +import { InvalidControlScrollDirective } from './main/directive/invalid-control-scroll.directive'; import { SelectProjectComponent } from './workspace/resource/resource-instance-form/select-project/select-project.component'; import { SelectOntologyComponent } from './workspace/resource/resource-instance-form/select-ontology/select-ontology.component'; import { SelectResourceClassComponent } from './workspace/resource/resource-instance-form/select-resource-class/select-resource-class.component'; @@ -145,6 +147,8 @@ export function HttpLoaderFactory(httpClient: HttpClient) { HelpComponent, FooterComponent, ExternalLinksDirective, + InvalidControlScrollContainerDirective, + InvalidControlScrollDirective, ResourceInstanceFormComponent, SelectProjectComponent, SelectOntologyComponent, diff --git a/src/app/main/dialog/dialog.component.html b/src/app/main/dialog/dialog.component.html index 45a19c019c..17d707b568 100644 --- a/src/app/main/dialog/dialog.component.html +++ b/src/app/main/dialog/dialog.component.html @@ -232,7 +232,7 @@ -
+
diff --git a/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts b/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts new file mode 100644 index 0000000000..1e6271faca --- /dev/null +++ b/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts @@ -0,0 +1,8 @@ +import { InvalidControlScrollContainerDirective } from './invalid-control-scroll-container.directive'; + +describe('InvalidControlScrollContainerDirective', () => { + it('should create an instance', () => { + const directive = new InvalidControlScrollContainerDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/main/directive/invalid-control-scroll-container.directive.ts b/src/app/main/directive/invalid-control-scroll-container.directive.ts new file mode 100644 index 0000000000..2619d9272b --- /dev/null +++ b/src/app/main/directive/invalid-control-scroll-container.directive.ts @@ -0,0 +1,12 @@ +import { Directive, ElementRef } from '@angular/core'; + +@Directive({ + selector: '[appInvalidControlScrollContainer]' +}) +export class InvalidControlScrollContainerDirective { + + readonly containerEl: HTMLElement = this._el.nativeElement; + + constructor(private _el: ElementRef) { } + +} diff --git a/src/app/main/directive/invalid-control-scroll.directive.spec.ts b/src/app/main/directive/invalid-control-scroll.directive.spec.ts new file mode 100644 index 0000000000..d5c0ad3800 --- /dev/null +++ b/src/app/main/directive/invalid-control-scroll.directive.spec.ts @@ -0,0 +1,8 @@ +import { InvalidControlScrollDirective } from './invalid-control-scroll.directive'; + +describe('InvalidControlScrollDirective', () => { + it('should create an instance', () => { + const directive = new InvalidControlScrollDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/main/directive/invalid-control-scroll.directive.ts b/src/app/main/directive/invalid-control-scroll.directive.ts new file mode 100644 index 0000000000..f04535428a --- /dev/null +++ b/src/app/main/directive/invalid-control-scroll.directive.ts @@ -0,0 +1,49 @@ +import { Directive, ElementRef, HostListener, Optional } from '@angular/core'; +import { FormGroupDirective } from '@angular/forms'; +import { fromEvent } from 'rxjs/internal/observable/fromEvent'; +import { debounceTime } from 'rxjs/internal/operators/debounceTime'; +import { take } from 'rxjs/internal/operators/take'; + +import { InvalidControlScrollContainerDirective } from "./invalid-control-scroll-container.directive"; + +@Directive({ + selector: '[appInvalidControlScroll]' +}) +export class InvalidControlScrollDirective { + + private get containerEl(): any { + return this._scrollContainerDir ? this._scrollContainerDir.containerEl : window; + } + + constructor( + private _el: ElementRef, + private _formGroupDir: FormGroupDirective, + @Optional() private _scrollContainerDir: InvalidControlScrollContainerDirective + ) { } + + @HostListener("ngSubmit") submitData() { + if (this._formGroupDir.control.invalid) { + this._scrollToFirstInvalidControl(); + } + } + + private _scrollToFirstInvalidControl() { + const firstInvalidControl: HTMLElement = this._el.nativeElement.querySelector( + "form .ng-invalid" + ); + + this.containerEl.scrollIntoView({ + behavior: "smooth", + block: "start", + inline: "start" + }); + + fromEvent(this.containerEl, "scrollIntoView") + .pipe( + debounceTime(100), + take(1) + ) + .subscribe(() => firstInvalidControl.focus()); + } + +} diff --git a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html index adf906f806..9e95e1c35a 100644 --- a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html +++ b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.html @@ -50,7 +50,11 @@ - @@ -60,7 +64,7 @@ -
+ - diff --git a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts index 940c7d1bb1..474ffebee2 100644 --- a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts +++ b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts @@ -133,47 +133,52 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { submitData() { - const createResource = new CreateResource(); + if (this.propertiesParentForm.valid) { - createResource.label = this.resourceLabel; + const createResource = new CreateResource(); - createResource.type = this.selectedResourceClass.id; + createResource.label = this.resourceLabel; - createResource.attachedToProject = this.selectedProject; + createResource.type = this.selectedResourceClass.id; - this.selectPropertiesComponent.switchPropertiesComponent.forEach((child) => { - const createVal = child.createValueComponent.getNewValue(); - const iri = child.property.id; - if (createVal instanceof CreateValue) { - if (this.propertiesObj[iri]) { - // if a key already exists, add the createVal to the array - this.propertiesObj[iri].push(createVal); - } else { - // if no key exists, add one and add the createVal as the first value of the array - this.propertiesObj[iri] = [createVal]; + createResource.attachedToProject = this.selectedProject; + + this.selectPropertiesComponent.switchPropertiesComponent.forEach((child) => { + const createVal = child.createValueComponent.getNewValue(); + const iri = child.property.id; + if (createVal instanceof CreateValue) { + if (this.propertiesObj[iri]) { + // if a key already exists, add the createVal to the array + this.propertiesObj[iri].push(createVal); + } else { + // if no key exists, add one and add the createVal as the first value of the array + this.propertiesObj[iri] = [createVal]; + } } - } - }); + }); - createResource.properties = this.propertiesObj; + createResource.properties = this.propertiesObj; - this._dspApiConnection.v2.res.createResource(createResource).subscribe( - (res: ReadResource) => { - this.resource = res; + this._dspApiConnection.v2.res.createResource(createResource).subscribe( + (res: ReadResource) => { + this.resource = res; - // navigate to the resource viewer page - this._router.navigateByUrl('/resource', { skipLocationChange: true }).then(() => - this._router.navigate(['/resource/' + encodeURIComponent(this.resource.id)]) - ); + // navigate to the resource viewer page + this._router.navigateByUrl('/resource', { skipLocationChange: true }).then(() => + this._router.navigate(['/resource/' + encodeURIComponent(this.resource.id)]) + ); - this.closeDialog.emit(); - }, - (error: ApiResponseError) => { - this._errorHandler.showMessage(error); - } - ); + this.closeDialog.emit(); + }, + (error: ApiResponseError) => { + this._errorHandler.showMessage(error); + } + ); + } else { + this.propertiesParentForm.markAllAsTouched(); + } } /** @@ -352,6 +357,8 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { // filter out all props that cannot be edited or are link props this.properties = onto.getPropertyDefinitionsByType(ResourcePropertyDefinition).filter(prop => prop.isEditable && !prop.isLinkProperty); + // console.log('form valid?', this.selectResourceForm.valid); + // notifies the user that the selected resource does not have any properties defined yet. if (!this.selectPropertiesComponent && this.properties.length === 0) { this.errorMessage = 'No properties defined for the selected resource.'; diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts index 9d77e0492e..7c56162615 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts @@ -83,8 +83,6 @@ export class SelectPropertiesComponent implements OnInit { } else { this.isRequiredProp = false; } - } else { - console.log('false'); } } ); From 0c915044a8ace277fcb773dbd33a4e6567ec1c51 Mon Sep 17 00:00:00 2001 From: flaurens Date: Fri, 15 Jan 2021 15:26:45 +0100 Subject: [PATCH 07/14] fix(invalid-control-scroll-container): fix the spec file --- ...control-scroll-container.directive.spec.ts | 75 ++++++++++++++++++- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts b/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts index 1e6271faca..8aef9ea653 100644 --- a/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts +++ b/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts @@ -1,8 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { InvalidControlScrollContainerDirective } from './invalid-control-scroll-container.directive'; +import { InvalidControlScrollDirective } from './invalid-control-scroll.directive'; + +@Component({ + template: ` +
+ +
+ + +
+
+ + +
+
+ + +
+ + +
+ ` +}) +class TestLinkHostComponent implements OnInit { + + form: FormGroup; + + constructor() { } + + ngOnInit() { + this.form = new FormGroup({ + control1: new FormControl(), + control2: new FormControl(), + control3: new FormControl() + }); + } + + onSubmit() { + console.log('form submitted'); + } +} describe('InvalidControlScrollContainerDirective', () => { - it('should create an instance', () => { - const directive = new InvalidControlScrollContainerDirective(); - expect(directive).toBeTruthy(); - }); + + let testHostComponent: TestLinkHostComponent; + let testHostFixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + InvalidControlScrollContainerDirective, + InvalidControlScrollDirective, + TestLinkHostComponent + ], + imports: [ + ReactiveFormsModule + ] + }); + + testHostFixture = TestBed.createComponent(TestLinkHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + + expect(testHostComponent).toBeTruthy(); + + }); + + it('should create an instance', () => { + expect(testHostComponent).toBeTruthy(); + }); }); From a3075c769657a9ee063625923729b1bdcb996813 Mon Sep 17 00:00:00 2001 From: flaurens Date: Fri, 15 Jan 2021 15:38:29 +0100 Subject: [PATCH 08/14] fix(invalid-control-scroll): fix basic test --- .../invalid-control-scroll.directive.spec.ts | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/src/app/main/directive/invalid-control-scroll.directive.spec.ts b/src/app/main/directive/invalid-control-scroll.directive.spec.ts index d5c0ad3800..f86c835ef0 100644 --- a/src/app/main/directive/invalid-control-scroll.directive.spec.ts +++ b/src/app/main/directive/invalid-control-scroll.directive.spec.ts @@ -1,8 +1,71 @@ +import { Component, OnInit } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { InvalidControlScrollDirective } from './invalid-control-scroll.directive'; +@Component({ + template: ` +
+
+ + +
+
+ + +
+
+ + +
+ +
+ ` +}) +class TestLinkHostComponent implements OnInit { + + form: FormGroup; + + constructor() { } + + ngOnInit() { + this.form = new FormGroup({ + control1: new FormControl(), + control2: new FormControl(), + control3: new FormControl() + }); + } + + onSubmit() { + console.log('form submitted'); + } +} + describe('InvalidControlScrollDirective', () => { - it('should create an instance', () => { - const directive = new InvalidControlScrollDirective(); - expect(directive).toBeTruthy(); - }); + + let testHostComponent: TestLinkHostComponent; + let testHostFixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + InvalidControlScrollDirective, + TestLinkHostComponent + ], + imports: [ + ReactiveFormsModule + ] + }); + + testHostFixture = TestBed.createComponent(TestLinkHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + + expect(testHostComponent).toBeTruthy(); + + }); + + it('should create an instance', () => { + expect(testHostComponent).toBeTruthy(); + }); }); From e088d5e77f2259ce47727e306ad2cba353de6b38 Mon Sep 17 00:00:00 2001 From: flaurens Date: Mon, 18 Jan 2021 15:30:51 +0100 Subject: [PATCH 09/14] refactor(invalid-control-scroll): simplify the directives + update scrolling config --- src/app/app.module.ts | 2 - src/app/main/dialog/dialog.component.html | 2 +- ...control-scroll-container.directive.spec.ts | 75 ------------------- ...alid-control-scroll-container.directive.ts | 12 --- .../invalid-control-scroll.directive.ts | 27 ++----- 5 files changed, 6 insertions(+), 112 deletions(-) delete mode 100644 src/app/main/directive/invalid-control-scroll-container.directive.spec.ts delete mode 100644 src/app/main/directive/invalid-control-scroll-container.directive.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7c58929c4d..7aeb0f7f3f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -72,7 +72,6 @@ import { ResultsComponent } from './workspace/results/results.component'; import { environment } from '../environments/environment'; import { ExternalLinksDirective } from './main/directive/external-links.directive'; -import { InvalidControlScrollContainerDirective } from './main/directive/invalid-control-scroll-container.directive'; import { InvalidControlScrollDirective } from './main/directive/invalid-control-scroll.directive'; import { SelectProjectComponent } from './workspace/resource/resource-instance-form/select-project/select-project.component'; import { SelectOntologyComponent } from './workspace/resource/resource-instance-form/select-ontology/select-ontology.component'; @@ -147,7 +146,6 @@ export function HttpLoaderFactory(httpClient: HttpClient) { HelpComponent, FooterComponent, ExternalLinksDirective, - InvalidControlScrollContainerDirective, InvalidControlScrollDirective, ResourceInstanceFormComponent, SelectProjectComponent, diff --git a/src/app/main/dialog/dialog.component.html b/src/app/main/dialog/dialog.component.html index 17d707b568..45a19c019c 100644 --- a/src/app/main/dialog/dialog.component.html +++ b/src/app/main/dialog/dialog.component.html @@ -232,7 +232,7 @@ -
+
diff --git a/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts b/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts deleted file mode 100644 index 8aef9ea653..0000000000 --- a/src/app/main/directive/invalid-control-scroll-container.directive.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { InvalidControlScrollContainerDirective } from './invalid-control-scroll-container.directive'; -import { InvalidControlScrollDirective } from './invalid-control-scroll.directive'; - -@Component({ - template: ` -
-
-
- - -
-
- - -
-
- - -
- -
-
- ` -}) -class TestLinkHostComponent implements OnInit { - - form: FormGroup; - - constructor() { } - - ngOnInit() { - this.form = new FormGroup({ - control1: new FormControl(), - control2: new FormControl(), - control3: new FormControl() - }); - } - - onSubmit() { - console.log('form submitted'); - } -} - -describe('InvalidControlScrollContainerDirective', () => { - - let testHostComponent: TestLinkHostComponent; - let testHostFixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ - InvalidControlScrollContainerDirective, - InvalidControlScrollDirective, - TestLinkHostComponent - ], - imports: [ - ReactiveFormsModule - ] - }); - - testHostFixture = TestBed.createComponent(TestLinkHostComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); - - expect(testHostComponent).toBeTruthy(); - - }); - - it('should create an instance', () => { - expect(testHostComponent).toBeTruthy(); - }); -}); diff --git a/src/app/main/directive/invalid-control-scroll-container.directive.ts b/src/app/main/directive/invalid-control-scroll-container.directive.ts deleted file mode 100644 index 2619d9272b..0000000000 --- a/src/app/main/directive/invalid-control-scroll-container.directive.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Directive, ElementRef } from '@angular/core'; - -@Directive({ - selector: '[appInvalidControlScrollContainer]' -}) -export class InvalidControlScrollContainerDirective { - - readonly containerEl: HTMLElement = this._el.nativeElement; - - constructor(private _el: ElementRef) { } - -} diff --git a/src/app/main/directive/invalid-control-scroll.directive.ts b/src/app/main/directive/invalid-control-scroll.directive.ts index f04535428a..9303c6ce75 100644 --- a/src/app/main/directive/invalid-control-scroll.directive.ts +++ b/src/app/main/directive/invalid-control-scroll.directive.ts @@ -1,24 +1,14 @@ -import { Directive, ElementRef, HostListener, Optional } from '@angular/core'; +import { Directive, ElementRef, HostListener } from '@angular/core'; import { FormGroupDirective } from '@angular/forms'; -import { fromEvent } from 'rxjs/internal/observable/fromEvent'; -import { debounceTime } from 'rxjs/internal/operators/debounceTime'; -import { take } from 'rxjs/internal/operators/take'; - -import { InvalidControlScrollContainerDirective } from "./invalid-control-scroll-container.directive"; @Directive({ selector: '[appInvalidControlScroll]' }) export class InvalidControlScrollDirective { - private get containerEl(): any { - return this._scrollContainerDir ? this._scrollContainerDir.containerEl : window; - } - constructor( private _el: ElementRef, - private _formGroupDir: FormGroupDirective, - @Optional() private _scrollContainerDir: InvalidControlScrollContainerDirective + private _formGroupDir: FormGroupDirective ) { } @HostListener("ngSubmit") submitData() { @@ -32,18 +22,11 @@ export class InvalidControlScrollDirective { "form .ng-invalid" ); - this.containerEl.scrollIntoView({ + firstInvalidControl.scrollIntoView({ behavior: "smooth", - block: "start", - inline: "start" + block: "nearest", + inline: "nearest" }); - - fromEvent(this.containerEl, "scrollIntoView") - .pipe( - debounceTime(100), - take(1) - ) - .subscribe(() => firstInvalidControl.focus()); } } From 8f1b7d6d6d4e9543207ab24bda99287a84f6646c Mon Sep 17 00:00:00 2001 From: flaurens Date: Mon, 18 Jan 2021 15:49:56 +0100 Subject: [PATCH 10/14] refactor: clean up + add some explanations --- .../main/directive/invalid-control-scroll.directive.ts | 5 +++++ .../resource-instance-form.component.ts | 2 -- .../select-project/select-project.component.spec.ts | 8 +------- .../select-project/select-project.component.ts | 1 - 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/app/main/directive/invalid-control-scroll.directive.ts b/src/app/main/directive/invalid-control-scroll.directive.ts index 9303c6ce75..22bc93d3e3 100644 --- a/src/app/main/directive/invalid-control-scroll.directive.ts +++ b/src/app/main/directive/invalid-control-scroll.directive.ts @@ -17,11 +17,16 @@ export class InvalidControlScrollDirective { } } + /** + * Target the first invalid element of the resource-instance form (2nd panel property) and scroll to it + */ private _scrollToFirstInvalidControl() { + // target the first invalid form field const firstInvalidControl: HTMLElement = this._el.nativeElement.querySelector( "form .ng-invalid" ); + // scroll to the first invalid element in a smooth way firstInvalidControl.scrollIntoView({ behavior: "smooth", block: "nearest", diff --git a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts index 474ffebee2..e5fff9c68f 100644 --- a/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts +++ b/src/app/workspace/resource/resource-instance-form/resource-instance-form.component.ts @@ -357,8 +357,6 @@ export class ResourceInstanceFormComponent implements OnInit, OnDestroy { // filter out all props that cannot be edited or are link props this.properties = onto.getPropertyDefinitionsByType(ResourcePropertyDefinition).filter(prop => prop.isEditable && !prop.isLinkProperty); - // console.log('form valid?', this.selectResourceForm.valid); - // notifies the user that the selected resource does not have any properties defined yet. if (!this.selectPropertiesComponent && this.properties.length === 0) { this.errorMessage = 'No properties defined for the selected resource.'; diff --git a/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.spec.ts b/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.spec.ts index 350314b5ce..a017642761 100644 --- a/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.spec.ts +++ b/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.spec.ts @@ -60,13 +60,7 @@ describe('SelectProjectComponent', () => { FormsModule, BrowserAnimationsModule, MatFormFieldModule, - MatSelectModule ], - providers: [ - { - provide: DspApiConnectionToken, - useValue: new KnoraApiConnection(TestConfig.ApiConfig) - } - ] + MatSelectModule ] }) .compileComponents(); })); diff --git a/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.ts b/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.ts index 3020a5d9ad..4ec0f4c1c0 100644 --- a/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-project/select-project.component.ts @@ -36,7 +36,6 @@ export class SelectProjectComponent implements OnInit, OnDestroy, AfterViewInit projectChangesSubscription: Subscription; constructor( - @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, @Inject(FormBuilder) private _fb: FormBuilder) { } ngOnInit(): void { From 5dc1abe47113c193911fa658a41efaefb755cff2 Mon Sep 17 00:00:00 2001 From: flaurens Date: Tue, 19 Jan 2021 10:27:02 +0100 Subject: [PATCH 11/14] refactor(select-properties): update comment --- .../select-properties/select-properties.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts index 9fae108282..793aef53f7 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.spec.ts @@ -97,7 +97,7 @@ describe('SelectPropertiesComponent', () => { } } - // each property has two entries in the keyValuePair object + // each property has three entries in the keyValuePair object expect(propsArray.length).toEqual(18 * 3); }); From 7858d95ba0c97a4699ab7c1ef7f0ebe2780682d7 Mon Sep 17 00:00:00 2001 From: flaurens Date: Tue, 19 Jan 2021 10:35:10 +0100 Subject: [PATCH 12/14] refactor(select-properties): isPropRequired(id) returns a boolean --- .../select-properties/select-properties.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts index 7c56162615..d60a50222f 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts @@ -72,7 +72,7 @@ export class SelectPropertiesComponent implements OnInit { ); } - isPropRequired(propId: string): void { + isPropRequired(propId: string): boolean { if (this.resourceClass !== undefined && propId) { this.resourceClass.propertiesList.filter( (card: IHasProperty) => { @@ -86,6 +86,7 @@ export class SelectPropertiesComponent implements OnInit { } } ); + return this.isRequiredProp; } } From fb3a064a61d69ae1fd303f172554060a534bd4a0 Mon Sep 17 00:00:00 2001 From: flaurens Date: Tue, 19 Jan 2021 10:48:08 +0100 Subject: [PATCH 13/14] refactor(switch-properties): update comment --- .../switch-properties/switch-properties.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts index 5307a00706..44ba10e11d 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/switch-properties/switch-properties.component.ts @@ -28,7 +28,8 @@ export class SwitchPropertiesComponent implements OnInit { constructor() { } ngOnInit(): void { - // convert from boolean (1/0) to boolean (true/false) + // the input isRequiredProp provided by KeyValuePair is stored as a number + // a conversion from a number to a boolean is required by the input valueRequiredValidator this.isRequiredProp = !!+this.isRequiredProp; } From f13147757d793a80cd1a68c89e0e14a13646c034 Mon Sep 17 00:00:00 2001 From: flaurens Date: Tue, 19 Jan 2021 10:57:25 +0100 Subject: [PATCH 14/14] refactor(select-properties): update comments --- .../select-properties.component.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts index d60a50222f..cd58e271b2 100644 --- a/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts +++ b/src/app/workspace/resource/resource-instance-form/select-properties/select-properties.component.ts @@ -48,9 +48,10 @@ export class SelectPropertiesComponent implements OnInit { // see the deleteValue method below for more info this.propertyValuesKeyValuePair[prop.id + '-filtered'] = [0]; - // check the cardinality to know if the prop is required or not - this.isPropRequired(prop.id); - this.propertyValuesKeyValuePair[prop.id + '-cardinality'] = [this.isRequiredProp ? 1 : 0]; + // each property will also have a cardinality array to be used when marking a field as required + // see the isPropRequired method below for more info + this.isPropRequired(prop.id); + this.propertyValuesKeyValuePair[prop.id + '-cardinality'] = [this.isRequiredProp ? 1 : 0]; } } } @@ -72,6 +73,13 @@ export class SelectPropertiesComponent implements OnInit { ); } + /** + * Check the cardinality of a property + * If the cardinality is 1 or 1-N, the property will be marked as required + * If the cardinality is 0-1 or 0-N, the property will not be required + * + * @param propId property id + */ isPropRequired(propId: string): boolean { if (this.resourceClass !== undefined && propId) { this.resourceClass.propertiesList.filter( @@ -80,7 +88,7 @@ export class SelectPropertiesComponent implements OnInit { // cardinality 1 or 1-N if (card.cardinality === Cardinality._1 || card.cardinality === Cardinality._1_n) { this.isRequiredProp = true; - } else { + } else { // cardinality 0-1 or 0-N this.isRequiredProp = false; } }