Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(new-resource-form): make visible the required prop fields (DSP-1115) #342

Merged
merged 24 commits into from Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1099910
feat(select-properties): check if the prop is required according to i…
Dec 10, 2020
123c4a5
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Dec 11, 2020
4c643d3
test(select-properties): fix test
Dec 11, 2020
f5b6880
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Dec 16, 2020
d9e4531
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Dec 16, 2020
e365fe2
style(select-properties): fix the alignment label-asterisk
Dec 18, 2020
d9b906c
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Dec 18, 2020
a79b50d
feat(switch-properties): add input valueRequiredValidator
Dec 18, 2020
c505a4d
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Dec 18, 2020
cf2245f
fix(switch-properties): pass to the input valueRequiredValidator a va…
Jan 6, 2021
ee7c76c
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Jan 11, 2021
33a877d
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Jan 12, 2021
f279f17
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Jan 12, 2021
fcdd9f3
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Jan 13, 2021
bd96c39
Merge branch 'main' into wip/dsp-1115-mark-required-fields
flavens Jan 15, 2021
b0499b8
feat(resource-instance-form): scroll to the first invalid form field
Jan 15, 2021
0c91504
fix(invalid-control-scroll-container): fix the spec file
Jan 15, 2021
a3075c7
fix(invalid-control-scroll): fix basic test
Jan 15, 2021
e088d5e
refactor(invalid-control-scroll): simplify the directives + update sc…
Jan 18, 2021
8f1b7d6
refactor: clean up + add some explanations
Jan 18, 2021
5dc1abe
refactor(select-properties): update comment
Jan 19, 2021
7858d95
refactor(select-properties): isPropRequired(id) returns a boolean
Jan 19, 2021
fb3a064
refactor(switch-properties): update comment
Jan 19, 2021
f131477
refactor(select-properties): update comments
Jan 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Expand Up @@ -72,6 +72,7 @@ import { ResultsComponent } from './workspace/results/results.component';

import { environment } from '../environments/environment';
import { ExternalLinksDirective } from './main/directive/external-links.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';
Expand Down Expand Up @@ -145,6 +146,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
HelpComponent,
FooterComponent,
ExternalLinksDirective,
InvalidControlScrollDirective,
ResourceInstanceFormComponent,
SelectProjectComponent,
SelectOntologyComponent,
Expand Down
71 changes: 71 additions & 0 deletions src/app/main/directive/invalid-control-scroll.directive.spec.ts
@@ -0,0 +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: `
<form [formGroup]="form" (ngSubmit)="onSubmit()" appInvalidControlScroll>
<div class="form-group">
<label for="control1">Field 1</label>
<input class="form-control" formControlName="control1"/>
</div>
<div class="form-group">
<label for="control1">Field 2</label>
<input class="form-control" formControlName="control2"/>
</div>
<div class="form-group">
<label for="control1">Field 3</label>
<input class="form-control" formControlName="control3"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
`
})
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', () => {

let testHostComponent: TestLinkHostComponent;
let testHostFixture: ComponentFixture<TestLinkHostComponent>;

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();
});
});
37 changes: 37 additions & 0 deletions src/app/main/directive/invalid-control-scroll.directive.ts
@@ -0,0 +1,37 @@
import { Directive, ElementRef, HostListener } from '@angular/core';
import { FormGroupDirective } from '@angular/forms';

@Directive({
selector: '[appInvalidControlScroll]'
})
export class InvalidControlScrollDirective {

constructor(
private _el: ElementRef,
private _formGroupDir: FormGroupDirective
) { }

@HostListener("ngSubmit") submitData() {
if (this._formGroupDir.control.invalid) {
this._scrollToFirstInvalidControl();
}
}

/**
* 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",
inline: "nearest"
});
}

}
Expand Up @@ -50,7 +50,11 @@
</span>
<span class="fill-remaining-space"></span>
<span>
<button mat-raised-button type="button" color="primary" [disabled]="!selectResourceForm.valid || this.errorMessage"
<button
mat-raised-button
type="button"
color="primary"
[disabled]="!selectResourceForm.valid || this.errorMessage"
(click)="nextStep()" class="form-next">
Next
</button>
Expand All @@ -60,7 +64,7 @@
</form>

<!-- Step2: create property values and submit data -->
<form *ngIf="propertiesParentForm && !showNextStepForm" [formGroup]="propertiesParentForm" class="resource-instance-form stepTwo form-content" (ngSubmit)="submitData()">
<form *ngIf="propertiesParentForm && !showNextStepForm" [formGroup]="propertiesParentForm" class="resource-instance-form stepTwo form-content" (ngSubmit)="submitData()" appInvalidControlScroll>

<!-- create property values -->
<app-select-properties
Expand All @@ -84,7 +88,7 @@
</button>
<span class="fill-remaining-space"></span>
<span>
<button mat-raised-button type="submit" color="primary" [disabled]="!propertiesParentForm.valid" class="form-submit">
<button mat-raised-button type="submit" color="primary" class="form-submit">
{{ 'appLabels.form.action.submit' | translate}}
</button>
</span>
Expand Down
Expand Up @@ -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();
}
}

/**
Expand Down
Expand Up @@ -60,13 +60,7 @@ describe('SelectProjectComponent', () => {
FormsModule,
BrowserAnimationsModule,
MatFormFieldModule,
MatSelectModule ],
providers: [
{
provide: DspApiConnectionToken,
useValue: new KnoraApiConnection(TestConfig.ApiConfig)
}
]
MatSelectModule ]
})
.compileComponents();
}));
Expand Down
Expand Up @@ -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 {
Expand Down
Expand Up @@ -3,12 +3,17 @@
<div *ngFor="let prop of properties; let last = last;" [class.border-bottom]="!last">
<div class="property" *ngIf="!prop.isLinkProperty || prop">
<div class="property-label">
<h3 class="label mat-subheading-1"
<span>
<h3 class="label mat-subheading-1"
[class.label-info]="prop.comment"
[matTooltip]="prop.comment"
matTooltipPosition="above">
{{prop.label}}
</h3>
</span>
<span *ngIf="propertyValuesKeyValuePair[prop.id + '-cardinality'][0] === 1" class="propIsRequired">
*
</span>
</div>
<div class="property-value large-field">
<div *ngFor="let val of propertyValuesKeyValuePair[prop.id]; let i=index">
Expand All @@ -19,7 +24,8 @@
[property]="prop"
[parentResource]="parentResource"
[parentForm]="parentForm"
[formName]="prop.label + '_' + i">
[formName]="prop.label + '_' + i"
[isRequiredProp]="propertyValuesKeyValuePair[prop.id + '-cardinality']">
</app-switch-properties>
</div>
<div class="buttons">
Expand Down
Expand Up @@ -40,11 +40,21 @@

.label {
text-align: right;
display: block;
float: left;
width: 95%;
}

.label-info {
cursor: help;
}

.propIsRequired {
color: red;
display: block;
float: right;
width: 5%;
}
}

.property-value {
Expand Down
Expand Up @@ -97,8 +97,8 @@ describe('SelectPropertiesComponent', () => {
}
}

// each property has two entries in the keyValuePair object
expect(propsArray.length).toEqual(18 * 2);
// each property has three entries in the keyValuePair object
expect(propsArray.length).toEqual(18 * 3);
});

describe('Add/Delete functionality', () => {
Expand Down
@@ -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';

Expand Down Expand Up @@ -29,6 +29,8 @@ export class SelectPropertiesComponent implements OnInit {

addButtonIsVisible: boolean;

isRequiredProp: boolean;

constructor(private _valueService: ValueService) { }

ngOnInit() {
Expand All @@ -45,13 +47,19 @@ 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];

// 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];
}
}
}

this.parentResource.entityInfo = this.ontologyInfo;
}


/**
* Given a resource property, check if an add button should be displayed under the property values
*
Expand All @@ -65,6 +73,31 @@ 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(
(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 { // cardinality 0-1 or 0-N
this.isRequiredProp = false;
}
}
}
);
return this.isRequiredProp;
}
}

/**
* Called from the template when the user clicks on the add button
*/
Expand Down