/
resource-instance-form.component.ts
375 lines (303 loc) · 14.8 KB
/
resource-instance-form.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import {
ApiResponseData,
ApiResponseError,
Constants,
CreateResource,
CreateValue,
KnoraApiConnection,
OntologiesMetadata,
ProjectsResponse,
ReadOntology,
ReadResource,
ResourceClassAndPropertyDefinitions,
ResourceClassDefinition,
ResourcePropertyDefinition,
StoredProject,
UserResponse
} from '@dasch-swiss/dsp-js';
import {
DspApiConnectionToken,
Session,
SessionService
} from '@dasch-swiss/dsp-ui';
import { Subscription } from 'rxjs';
import { CacheService } from 'src/app/main/cache/cache.service';
import { ErrorHandlerService } from 'src/app/main/error/error-handler.service';
import { SelectOntologyComponent } from './select-ontology/select-ontology.component';
import { SelectPropertiesComponent } from './select-properties/select-properties.component';
import { SelectResourceClassComponent } from './select-resource-class/select-resource-class.component';
@Component({
selector: 'app-resource-instance-form',
templateUrl: './resource-instance-form.component.html',
styleUrls: ['./resource-instance-form.component.scss']
})
export class ResourceInstanceFormComponent implements OnInit, OnDestroy {
// output to close dialog
@Output() closeDialog: EventEmitter<any> = new EventEmitter<any>();
/**
* update title and subtitle in dialog header (by switching from step 1 (resource class) to step 2 (properties))
*/
@Output() updateParent: EventEmitter<{ title: string, subtitle: string }> = new EventEmitter<{ title: string, subtitle: string }>();
@ViewChild('selectProps') selectPropertiesComponent: SelectPropertiesComponent;
@ViewChild('selectResourceClass') selectResourceClassComponent: SelectResourceClassComponent;
@ViewChild('selectOntology') selectOntologyComponent: SelectOntologyComponent;
// forms
selectResourceForm: FormGroup;
propertiesParentForm: FormGroup;
// form validation status
formValid = false;
session: Session;
username: string;
showNextStepForm: boolean;
usersProjects: StoredProject[];
selectedProject: string;
ontologiesMetadata: OntologiesMetadata;
selectedOntology: string;
resourceClasses: ResourceClassDefinition[];
selectedResourceClass: ResourceClassDefinition;
resource: ReadResource;
resourceLabel: string;
properties: ResourcePropertyDefinition[];
ontologyInfo: ResourceClassAndPropertyDefinitions;
valueOperationEventSubscription: Subscription;
errorMessage: any;
propertiesObj = {};
constructor(
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
private _cache: CacheService,
private _errorHandler: ErrorHandlerService,
private _fb: FormBuilder,
private _router: Router,
private _session: SessionService
) {
this.session = this._session.getSession();
this.username = this.session.user.name;
}
ngOnInit(): void {
// parent form is empty, it gets passed to the child components
this.selectResourceForm = this._fb.group({});
this.propertiesParentForm = this._fb.group({});
// initialize projects to be used for the project selection in the creation form
this.initializeProjects();
// boolean to show only the first step of the form (= selectResourceForm)
this.showNextStepForm = true;
}
ngOnDestroy() {
// unsubscribe from the event bus when component is destroyed
if (this.valueOperationEventSubscription !== undefined) {
this.valueOperationEventSubscription.unsubscribe();
}
}
/**
* Go to the next form page: from project/onto/resource selections to properties form
*/
nextStep() {
this.showNextStepForm = !this.showNextStepForm;
// use response to go further with properties
this.updateParent.emit({ title: this.resourceLabel, subtitle: 'Define the properties of the resource' });
}
/**
* Go to previous step: from properties form back to project/onto/resource selections
*/
prevStep(ev: Event) {
ev.preventDefault();
this.updateParent.emit({ title: this.resourceLabel, subtitle: 'Create new resource' });
this.showNextStepForm = true;
}
submitData() {
if (this.propertiesParentForm.valid) {
const createResource = new CreateResource();
createResource.label = this.resourceLabel;
createResource.type = this.selectedResourceClass.id;
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;
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)])
);
this.closeDialog.emit();
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
} else {
this.propertiesParentForm.markAllAsTouched();
}
}
/**
* Get the user's project(s)
*/
initializeProjects(): void {
this.usersProjects = [];
if (this.username && this.session.user.sysAdmin === false) {
this._cache.get(this.username, this._dspApiConnection.admin.usersEndpoint.getUserByUsername(this.username)).subscribe(
(response: ApiResponseData<UserResponse>) => {
for (const project of response.body.user.projects) {
this.usersProjects.push(project);
}
// notifies the user that he/she is not part of any project
if (this.usersProjects.length === 0) {
this.errorMessage = 'You are not a part of any active projects.';
}
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
} else if (this.session.user.sysAdmin === true) {
this._dspApiConnection.admin.projectsEndpoint.getProjects().subscribe(
(response: ApiResponseData<ProjectsResponse>) => {
this.usersProjects = response.body.projects;
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
}
}
/**
* Get all the ontologies of the selected project
* @param projectIri
*/
selectOntologies(projectIri: string) {
if (projectIri) {
// if this method is called with the same value as the current selectedProject, there is no need to do anything
if (projectIri !== this.selectedProject) {
// any time the project is changed:
// reset any error message
this.errorMessage = undefined;
// reset the selected ontology because it will be invalid
this.selectedOntology = undefined;
// reset the ontologies metadata before it is generated again to trigger the select-ontology OnInit method
this.ontologiesMetadata = undefined;
// reset resourceClasses to hide the select-resource-class form in case it is already visible
this.resourceClasses = undefined;
// remove the form control to ensure the parent Formgroups validity is correct
// this will be added to the parent Formgroup again when the select-ontology OnInit method is called
this.selectResourceForm.removeControl('ontologies');
// assign the selected iri to selectedProject
this.selectedProject = projectIri;
this._dspApiConnection.v2.onto.getOntologiesByProjectIri(projectIri).subscribe(
(response: OntologiesMetadata) => {
// filter out system ontologies
response.ontologies = response.ontologies.filter(onto => onto.attachedToProject !== Constants.SystemProjectIRI);
this.ontologiesMetadata = response;
// notifies the user that the selected project does not have any data models defined yet.
if (!this.selectOntologyComponent && response.ontologies.length === 0) {
this.errorMessage = 'No data models defined for the select project.';
}
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
}
} else {
this.errorMessage = 'You are not a part of any active projects.';
}
}
/**
* Get all the resource classes of the selected ontology
* @param ontologyIri
*/
selectResourceClasses(ontologyIri: string) {
// reset errorMessage, it will be reassigned in the else clause if needed
this.errorMessage = undefined;
if (ontologyIri) {
// if this method is called with the same value as the current selectedOntology, there is no need to do anything
if (ontologyIri !== this.selectedOntology) {
// reset any error message
this.errorMessage = undefined;
// reset selectedResourceClass since it will be invalid
this.selectedResourceClass = undefined;
this.resourceLabel = undefined;
// remove the form control to ensure the parent Formgroups validity is correct
// this will be added to the parent Formgroup again when the select-resource-class OnInit method is called
this.selectResourceForm.removeControl('resources');
// if there is already a select-resource-class component (i.e. the user clicked the back button), reset the resource & label
if (this.selectResourceClassComponent) {
this.selectResourceClassComponent.form.controls.resources.setValue(null);
this.selectResourceClassComponent.form.controls.label.setValue(null);
// since the component already exists, we need to add the control back here as it is normally done in the OnInit of the component
this.selectResourceForm.addControl('resources', this.selectResourceClassComponent.form);
}
this.selectedOntology = ontologyIri;
this._dspApiConnection.v2.ontologyCache.getOntology(ontologyIri).subscribe(
(onto: Map<string, ReadOntology>) => {
this.resourceClasses = onto.get(ontologyIri).getClassDefinitionsByType(ResourceClassDefinition);
if (this.selectResourceClassComponent && this.resourceClasses.length === 1) {
// since the component already exists, the ngAfterInit method of the component will not be called so we must assign the value here manually
this.selectResourceClassComponent.form.controls.resources.setValue(this.resourceClasses[0].id);
}
// notifies the user that the selected ontology does not have any resource classes defined yet.
if ((!this.selectResourceClassComponent || this.selectOntologyComponent.form.controls.ontologies.valueChanges) && this.resourceClasses.length === 0) {
this.errorMessage = 'No resources defined for the selected ontology.';
}
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
}
} else {
this.errorMessage = 'No ontology defined for the selected project.';
}
}
/**
* Get the resource label typed in the form in select-resource-class
* @param label
*/
getResourceLabel(label: string) {
this.resourceLabel = label;
}
/**
* Get all the properties of the selected resource class
* @param resourceClassIri
*/
selectProperties(resourceClassIri: string) {
// reset errorMessage, it will be reassigned in the else clause if needed
this.errorMessage = undefined;
// if the client undoes the selection of a resource class, use the active ontology as a fallback
if (resourceClassIri === null) {
this.selectResourceClasses(this.selectedOntology);
} else if (resourceClassIri) {
this._dspApiConnection.v2.ontologyCache.getResourceClassDefinition(resourceClassIri).subscribe(
(onto: ResourceClassAndPropertyDefinitions) => {
this.ontologyInfo = onto;
this.selectedResourceClass = onto.classes[resourceClassIri];
// filter out all props that cannot be edited or are link props
this.properties = onto.getPropertyDefinitionsByType(ResourcePropertyDefinition).filter(prop => prop.isEditable && !prop.isLinkProperty);
// 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.';
}
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
} else {
this.errorMessage = 'No resource class defined for the selected ontology.';
}
}
}