Skip to content
Permalink
Browse files
feat(ontology): bring back the name input field (DEV-157) (#559)
* feat(ontology): bring back the name input field (DEV-157)

* refactor(ontology): clean up prop form

* fix(string-literal): correct string literal touched handler

* style(ontology): fix field error in case of string literal

* refactor(ontology): clean up template

* test(ontology): fix tests
  • Loading branch information
kilchenmann committed Oct 21, 2021
1 parent bc210ef commit 51e539d88553a635bb4663dc8d185d79699ec03d
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 80 deletions.
@@ -144,7 +144,7 @@ export class StringLiteralInputComponent implements OnInit, OnChanges {

const form = this.form;
const control = form.get('text');
this.touched.emit(control && control.dirty);
this.touched.emit(control.dirty || control.touched);

this.updateStringLiterals(this.language, this.form.controls.text.value);

@@ -13,6 +13,7 @@ import { CacheService } from 'src/app/main/cache/cache.service';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { existingNamesValidator } from 'src/app/main/directive/existing-name/existing-name.directive';
import { ErrorHandlerService } from 'src/app/main/error/error-handler.service';
import { CustomRegex } from 'src/app/workspace/resource/values/custom-regex';
import { OntologyService } from '../ontology.service';

export interface NewOntology {
@@ -52,9 +53,6 @@ export class OntologyFormComponent implements OnInit {

lastModificationDate: string;

// regex to check ontology name: shouldn't start with a number or with 'v' followed by a number, spaces or special characters are not allowed
nameRegex = /^(?![vV]+[0-9])+^([a-zA-Z])[a-zA-Z0-9_.-]*$/;

// ontology name must not contain one of the following words
forbiddenNames: string[] = [
'knora',
@@ -183,7 +181,7 @@ export class OntologyFormComponent implements OnInit {
Validators.minLength(this.nameMinLength),
Validators.maxLength(this.nameMaxLength),
existingNamesValidator(this.existingNames),
Validators.pattern(this.nameRegex)
Validators.pattern(CustomRegex.ID_NAME_REGEX)
]),
label: new FormControl({
value: this.ontologyLabel, disabled: false
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Cardinality } from '@dasch-swiss/dsp-js';
import { Cardinality, Constants } from '@dasch-swiss/dsp-js';

/**
* helper methods for the ontology editor
@@ -46,6 +46,17 @@ export class OntologyService {
return array[pos].toLowerCase();
}

/**
* get the name from the iri
* @param iri
* @returns name from iri
*/
getNameFromIri(iri: string): string {
const array = iri.split(Constants.HashDelimiter);

return array[1];
}

/**
* convert cardinality values (multiple? & required?) from form to DSP-JS cardinality enum 1-n, 0-n, 1, 0-1
* @param {boolean} multiple
@@ -4,15 +4,17 @@
<form [formGroup]="propertyForm" class="form-content">

<p *ngIf="resClassIri && propertyInfo.propDef" class="note warning mat-caption center">
This property already exists. If you want to modify it, go to the "properties" view.
You're adding an already existing property to this class.
The property can't modified here. If you want to modify it,
go to the "properties" view.
<!-- Be careful when editing it, it could have an effect in other resource classes if it is used there. -->
</p>

<div class="center">

<!-- property type -->
<mat-form-field *ngIf="propertyInfo.propType" class="large-field property-type">
<span matPrefix class="property-type-icon">
<mat-form-field *ngIf="propertyInfo.propType" class="large-field ontology-form-field">
<span matPrefix class="ontology-prefix-icon">
<mat-icon>{{propertyForm.controls['propType'].value.icon}}</mat-icon>&nbsp;
</span>
<mat-label>Property type</mat-label>
@@ -31,20 +33,40 @@
</mat-select>
</mat-form-field>

<!-- name -->
<mat-form-field class="large-field ontology-form-field">
<span matPrefix class="ontology-prefix-icon">
<mat-icon>fingerprint</mat-icon>&nbsp;
</span>
<mat-label>Property name *</mat-label>
<input matInput formControlName="name" >
<mat-hint class="ontology-error-with-prefix" *ngIf="formErrors.name">
{{formErrors.name}}
</mat-hint>
</mat-form-field>

<!-- label -->
<div class="large-field string-literal-container">
<app-string-literal-input [placeholder]="'Property label *'" [value]="labels" [disabled]="(resClassIri && propertyInfo.propDef)"
(dataChanged)="handleData($event, 'labels')">
<app-string-literal-input
[placeholder]="'Property label *'"
[value]="labels"
[disabled]="(resClassIri && propertyInfo.propDef)"
(touched)="labelsTouched = $event"
(dataChanged)="handleData($event, 'label')">
</app-string-literal-input>
<mat-hint class="string-literal-error" *ngIf="!labels.length">
Label is required
<mat-hint class="string-literal-error" *ngIf="formErrors.label">
{{ formErrors.label }}
</mat-hint>
</div>

<!-- comment/description -->
<div class="large-field string-literal-container">
<app-string-literal-input [textarea]="true" [placeholder]="'Comment'" [value]="comments" [disabled]="(resClassIri && propertyInfo.propDef)"
(dataChanged)="handleData($event, 'comments')">
<app-string-literal-input
[placeholder]="'Comment'"
[value]="comments"
[disabled]="(resClassIri && propertyInfo.propDef)"
[textarea]="true"
(dataChanged)="handleData($event, 'comment')">
</app-string-literal-input>
</div>

@@ -26,23 +26,6 @@
}
}

.property-type {
.property-type-icon {
width: 36px;
padding: 0 8px;
display: block;
}

mat-label,
mat-select,
input {
margin-left: 12px;
}
mat-select {
width: calc(100% - 12px);
}
}

.cardinality {

.mat-slide-toggle {
@@ -243,17 +243,17 @@ describe('PropertyFormComponent', () => {

});

it('should update labels when the value changes', () => {
it('should update labels when the value changes; error message should disapear', () => {

const hostCompDe = simpleTextHostFixture.debugElement;
const submitButton: DebugElement = hostCompDe.query(By.css('button.submit'));
expect(submitButton.nativeElement.innerText).toContain('Update');

simpleTextHostComponent.propertyFormComponent.handleData([], 'labels');
simpleTextHostComponent.propertyFormComponent.handleData([{ language: 'de', value: 'New Label' }], 'label');
simpleTextHostFixture.detectChanges();

const formInvalidMessageDe: DebugElement = hostCompDe.query(By.css('mat-hint'));
expect(formInvalidMessageDe.nativeElement.innerText).toEqual(' Label is required ');
const formInvalidMessageDe: DebugElement = hostCompDe.query(By.css('string-literal-error'));
expect(formInvalidMessageDe).toBeFalsy();

});

@@ -20,7 +20,9 @@ import {
} from '@dasch-swiss/dsp-js';
import { CacheService } from 'src/app/main/cache/cache.service';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { existingNamesValidator } from 'src/app/main/directive/existing-name/existing-name.directive';
import { ErrorHandlerService } from 'src/app/main/error/error-handler.service';
import { CustomRegex } from 'src/app/workspace/resource/values/custom-regex';
import { AutocompleteItem } from 'src/app/workspace/search/advanced-search/resource-and-property-selection/search-select-property/specify-property-value/operator';
import { DefaultProperties, DefaultProperty, PropertyCategory, PropertyInfoObject } from '../default-data/default-properties';
import { OntologyService } from '../ontology.service';
@@ -65,11 +67,17 @@ export class PropertyFormComponent implements OnInit {
propertyForm: FormGroup;

formErrors = {
'name': '',
'label': '',
'guiAttr': ''
};

validationMessages = {
'name': {
'required': 'Name is required.',
'existingName': 'This name is already taken. Please choose another one.',
'pattern': 'Name shouldn\'t start with a number or v + number and spaces or special characters (except dash, dot and underscore) are not allowed.'
},
'label': {
'required': 'Label is required.',
},
@@ -101,17 +109,23 @@ export class PropertyFormComponent implements OnInit {
error = false;

labels: StringLiteral[] = [];
labelsTouched: boolean;
comments: StringLiteral[] = [];
guiAttributes: string[] = [];

// list of existing property names
existingNames: [RegExp] = [
new RegExp('anEmptyRegularExpressionWasntPossible')
];

dspConstants = Constants;

constructor(
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
private _cache: CacheService,
private _errorHandler: ErrorHandlerService,
private _fb: FormBuilder,
private _ontologyService: OntologyService
private _os: OntologyService
) { }

ngOnInit() {
@@ -127,6 +141,16 @@ export class PropertyFormComponent implements OnInit {
// a) in case of link value:
// set list of resource classes from response; needed for linkValue
this.resourceClasses = response.getAllClassDefinitions();

// set list of all existing property names to avoid same name twice
Object.entries(this.ontology.properties).forEach(
([key]) => {
const name = this._os.getNameFromIri(key);
this.existingNames.push(
new RegExp('(?:^|W)' + name.toLowerCase() + '(?:$|W)')
);
}
);
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
@@ -168,7 +192,7 @@ export class PropertyFormComponent implements OnInit {

// slice array
// this slice value will be kept
// because there was the idea to shorten the array of restrcited elements
// because there was the idea to shorten the array of restricted elements
// in case e.g. richtext can't be changed to simple text, then we shouldn't list the simple text item
const slice = 0;

@@ -181,6 +205,14 @@ export class PropertyFormComponent implements OnInit {
}

this.propertyForm = this._fb.group({
'name': new FormControl({
value: (this.propertyInfo.propDef ? this._os.getNameFromIri(this.propertyInfo.propDef.id) : ''),
disabled: this.propertyInfo.propDef
}, [
Validators.required,
existingNamesValidator(this.existingNames),
Validators.pattern(CustomRegex.ID_NAME_REGEX)
]),
'propType': new FormControl({
value: this.propertyInfo.propType,
disabled: disablePropType || this.resClassIri
@@ -217,11 +249,9 @@ export class PropertyFormComponent implements OnInit {
return;
}

const form = this.propertyForm;

Object.keys(this.formErrors).map(field => {
this.formErrors[field] = '';
const control = form.get(field);
const control = this.propertyForm.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
Object.keys(control.errors).map(key => {
@@ -232,14 +262,20 @@ export class PropertyFormComponent implements OnInit {
});
}

handleData(data: StringLiteral[], type: string) {
handleData(data: StringLiteral[], type: 'label' | 'comment') {

switch (type) {
case 'labels':
case 'label':
this.labels = data;
const messages = this.validationMessages[type];
this.formErrors[type] = '';

if (this.labelsTouched && !this.labels.length) {
this.formErrors[type] = messages['required'];
}
break;

case 'comments':
case 'comment':
this.comments = data;
break;
}
@@ -420,7 +456,7 @@ export class PropertyFormComponent implements OnInit {
// create mode: new property incl. gui type and attribute
// submit property
// set resource property name / id: randomized string
const uniquePropName: string = this._ontologyService.setUniqueName(this.ontology.id);
// const uniquePropName: string = this._os.setUniqueName(this.ontology.id);

const onto = new UpdateOntology<CreateResourceProperty>();

@@ -429,7 +465,7 @@ export class PropertyFormComponent implements OnInit {

// prepare payload for property
const newResProp = new CreateResourceProperty();
newResProp.name = uniquePropName;
newResProp.name = this.propertyForm.controls['name'].value;
newResProp.label = this.labels;
newResProp.comment = (this.comments.length ? this.comments : this.labels);
const guiAttr = this.propertyForm.controls['guiAttr'].value;
@@ -488,7 +524,7 @@ export class PropertyFormComponent implements OnInit {

const propCard: IHasProperty = {
propertyIndex: prop.id,
cardinality: this._ontologyService.translateCardinality(this.propertyForm.value.multiple, this.propertyForm.value.required),
cardinality: this._os.translateCardinality(this.propertyForm.value.multiple, this.propertyForm.value.required),
guiOrder: this.guiOrder // add new property to the end of current list of properties
};

@@ -5,11 +5,26 @@
<div class="resource-class-data">
<div class="center more-space-top">

<!-- name -->
<mat-form-field class="large-field ontology-form-field">
<span matPrefix class="ontology-prefix-icon">
<mat-icon>fingerprint</mat-icon>&nbsp;
</span>
<mat-label>Property name *</mat-label>
<input matInput formControlName="name" >
<mat-hint class="ontology-error-with-prefix" *ngIf="formErrors.name">
{{formErrors.name}}
</mat-hint>
</mat-form-field>

<!-- label -->
<div class="large-field string-literal-container">
<app-string-literal-input [placeholder]="'Label *'" [value]="resourceClassLabels"
<div class="large-field string-literal-container more-space-top">
<app-string-literal-input
[placeholder]="'Label *'"
[value]="resourceClassLabels"
(enter)="submitData()"
(dataChanged)="handleData($event, 'labels')">
(touched)="resourceClassLabelsTouched = $event"
(dataChanged)="handleData($event, 'label')">
</app-string-literal-input>
<mat-hint class="string-literal-error" *ngIf="formErrors.label">
{{ formErrors.label }}
@@ -18,8 +33,16 @@

<!-- description -->
<div class="large-field string-literal-container more-space-top">
<app-string-literal-input [placeholder]="'Comment *'" [value]="resourceClassComments" [textarea]="true"
(dataChanged)="handleData($event, 'comments')"></app-string-literal-input>
<app-string-literal-input
[placeholder]="'Comment *'"
[value]="resourceClassComments"
[textarea]="true"
(touched)="resourceClassCommentsTouched = $event"
(dataChanged)="handleData($event, 'comment')">
</app-string-literal-input>
<mat-hint class="string-literal-error" *ngIf="formErrors.comment">
{{ formErrors.comment }}
</mat-hint>
</div>
</div>

0 comments on commit 51e539d

Please sign in to comment.