Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • Experimental/Bugfixes
  • Hotfix/1129-fixValidation
  • Hotfix/1140-fixFixedValues
  • Hotfix/1142-fixFixedValues
  • Hotfix/1197-Datepicker
  • Hotfix/1216-fixDateDesign
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2417-emptyMetadataValidation
  • Hotfix/2504-formGen
  • Hotfix/2504-formGenerator
  • Hotfix/2681-validationErrors
  • Hotfix/2955-storingFail
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/845-someUIChanges
  • Hotfix/920-multiSelect
  • Hotfix/xxxx-infiniteLoops
  • Hotfix/xxxx-nodePath
  • Hotfix/xxxx-notEmptyFields
  • Issue/1607-dateTimeOffset
  • Issue/1607-newDateTimeOffset
  • Issue/1804-fixedValueFix
  • Issue/1804-fixedValueFixOld
  • Issue/1826-versioning
  • Issue/1938-internalHandling
  • Issue/1987-shIn
  • Issue/2002-migrateResourceCreate
  • Issue/2035-rcv
  • Issue/2117-ProjectandResourceCreateInfoIcons
  • Issue/2139-detailViewing
  • Issue/2262-frontendWorkspace
  • Issue/2309-docs
  • Issue/2318-fixedValuesFix
  • Issue/2341-description
  • Issue/2345-dealWithWeirdLists
  • Issue/2353-dropShapeFix
  • Issue/2408-hasValue
  • Issue/2435-userApi
  • Issue/2516-validationUpdate
  • Issue/2525-fixedFixValues
  • Issue/2551-enhanceSymbolDescriptionsInApplicationProfile
  • Issue/2598-vue3
  • Issue/2703-vocabularyList
  • Issue/2729-fixSlowLoadingOfInstances
  • Issue/2759-showMissingField
  • Issue/2804-templateUI
  • Issue/2805-ignoreTemplatingValues
  • Issue/2851-fixBooleanInFormGenerator
  • Issue/2920-fixRemovingValues
  • Issue/2943-uiFeedback
  • Issue/2960-fixValidationForAP
  • Issue/2960-infiniteLoop
  • Issue/43-saveButton
  • Issue/xxxx-correctTargetClass
  • Issue/xxxx-extensions
  • Issue/xxxx-infiniteLoop
  • Issue/xxxx-instances
  • Issue/xxxx-optimizedBuild
  • Issue/xxxx-reduceBootstrapVueBundle
  • Product/1107-frontendPerformance
  • Product/1210-inheritance
  • Product/1215-gitlabCleanUp
  • Product/1532-formGeneratorFeatures
  • Product/1573-ReadOnlyResources
  • Product/1576-formGeneratorMultiselect
  • Product/510-niceProjectUrl
  • Product/619-clientValidation
  • Product/791-uiAdaptions
  • Product/801-refactorFileMetadata
  • Product/815-cleanupApplicationProfiles
  • Sprint/2020-09
  • Sprint/2020-10
  • Sprint/2020-11
  • Sprint/2020-12
  • Sprint/2020-13
  • Sprint/2020-14
  • Sprint/2020-18
  • Sprint/2020-19
  • Sprint/2021-12
  • Sprint/2021-13
  • Sprint/2021-14
  • Sprint/2021-15
  • Sprint/2021-2022
  • Sprint/2021-22
  • Sprint/2022-01
  • Test/vue-demi
  • Topic/1065-clientValidation
  • Topic/1227-frontendPerformance
  • Topic/1229-inheritance
  • Topic/1533-formGeneratorCleanup
  • Topic/1535-formGeneratorMultiselect
  • Topic/1535-linting
  • Topic/795-uiAdaptions
  • Topic/804-niceProjectUrl
  • Topic/846-cleanupApplicationProfiles
  • Topic/853-refactorFileMetadataFE
  • dev
  • gitkeep
  • gitpod
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.11.0
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.17.0
  • v1.18.0
  • v1.2.0
  • v1.3.0
  • v1.3.1
  • v1.3.2
  • v1.3.3
  • v1.4.0
  • v1.5.0
  • v1.6.0
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.8.3
  • v1.8.4
  • v1.9.0
  • v1.9.1
  • v1.9.2
  • v1.9.3
  • v2.0.0
  • v2.1.0
  • v3.0.0
  • v3.0.1
  • v3.0.10
  • v3.0.11
  • v3.0.12
  • v3.0.2
  • v3.0.3
  • v3.0.4
  • v3.0.5
  • v3.0.6
  • v3.0.7
  • v3.0.8
  • v3.0.9
  • v3.1.0
  • v3.1.1
  • v3.1.2
  • v3.1.3
  • v3.1.4
  • v3.2.0
  • v3.2.1
  • v3.2.2
  • v3.3.0
  • v3.4.0
  • v3.5.0
  • v3.5.1
  • v3.5.2
  • v3.5.3
  • v3.5.4
  • v3.5.5
  • v3.5.6
  • v3.5.7
  • v3.6.0
  • v3.6.1
  • v3.6.2
  • v3.6.3
  • v4.0.0
  • v4.0.1
  • v4.0.2
  • v4.0.3
  • v4.0.4
  • v4.0.5
175 results

Target

Select target project
  • coscine/frontend/libraries/form-generator
1 result
Select Git revision
  • Experimental/Bugfixes
  • Hotfix/1129-fixValidation
  • Hotfix/1140-fixFixedValues
  • Hotfix/1142-fixFixedValues
  • Hotfix/1197-Datepicker
  • Hotfix/1216-fixDateDesign
  • Hotfix/2233-fixMe
  • Hotfix/2258-saveButtonWorksAsExpected
  • Hotfix/2296-selectedValuesNotReturned
  • Hotfix/2417-emptyMetadataValidation
  • Hotfix/2504-formGen
  • Hotfix/2504-formGenerator
  • Hotfix/2681-validationErrors
  • Hotfix/2955-storingFail
  • Hotfix/2957-styleAndUpgrade
  • Hotfix/845-someUIChanges
  • Hotfix/920-multiSelect
  • Hotfix/xxxx-infiniteLoops
  • Hotfix/xxxx-nodePath
  • Hotfix/xxxx-notEmptyFields
  • Issue/1607-dateTimeOffset
  • Issue/1607-newDateTimeOffset
  • Issue/1804-fixedValueFix
  • Issue/1804-fixedValueFixOld
  • Issue/1826-versioning
  • Issue/1938-internalHandling
  • Issue/1987-shIn
  • Issue/2002-migrateResourceCreate
  • Issue/2035-rcv
  • Issue/2117-ProjectandResourceCreateInfoIcons
  • Issue/2139-detailViewing
  • Issue/2262-frontendWorkspace
  • Issue/2309-docs
  • Issue/2318-fixedValuesFix
  • Issue/2341-description
  • Issue/2345-dealWithWeirdLists
  • Issue/2353-dropShapeFix
  • Issue/2408-hasValue
  • Issue/2435-userApi
  • Issue/2516-validationUpdate
  • Issue/2525-fixedFixValues
  • Issue/2551-enhanceSymbolDescriptionsInApplicationProfile
  • Issue/2598-vue3
  • Issue/2703-vocabularyList
  • Issue/2729-fixSlowLoadingOfInstances
  • Issue/2759-showMissingField
  • Issue/2804-templateUI
  • Issue/2805-ignoreTemplatingValues
  • Issue/2851-fixBooleanInFormGenerator
  • Issue/2920-fixRemovingValues
  • Issue/2943-uiFeedback
  • Issue/2960-fixValidationForAP
  • Issue/2960-infiniteLoop
  • Issue/43-saveButton
  • Issue/xxxx-correctTargetClass
  • Issue/xxxx-extensions
  • Issue/xxxx-infiniteLoop
  • Issue/xxxx-instances
  • Issue/xxxx-optimizedBuild
  • Issue/xxxx-reduceBootstrapVueBundle
  • Product/1107-frontendPerformance
  • Product/1210-inheritance
  • Product/1215-gitlabCleanUp
  • Product/1532-formGeneratorFeatures
  • Product/1573-ReadOnlyResources
  • Product/1576-formGeneratorMultiselect
  • Product/510-niceProjectUrl
  • Product/619-clientValidation
  • Product/791-uiAdaptions
  • Product/801-refactorFileMetadata
  • Product/815-cleanupApplicationProfiles
  • Sprint/2020-09
  • Sprint/2020-10
  • Sprint/2020-11
  • Sprint/2020-12
  • Sprint/2020-13
  • Sprint/2020-14
  • Sprint/2020-18
  • Sprint/2020-19
  • Sprint/2021-12
  • Sprint/2021-13
  • Sprint/2021-14
  • Sprint/2021-15
  • Sprint/2021-2022
  • Sprint/2021-22
  • Sprint/2022-01
  • Test/vue-demi
  • Topic/1065-clientValidation
  • Topic/1227-frontendPerformance
  • Topic/1229-inheritance
  • Topic/1533-formGeneratorCleanup
  • Topic/1535-formGeneratorMultiselect
  • Topic/1535-linting
  • Topic/795-uiAdaptions
  • Topic/804-niceProjectUrl
  • Topic/846-cleanupApplicationProfiles
  • Topic/853-refactorFileMetadataFE
  • dev
  • gitkeep
  • gitpod
  • v1.0.0
  • v1.1.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.11.0
  • v1.12.0
  • v1.13.0
  • v1.14.0
  • v1.15.0
  • v1.15.1
  • v1.16.0
  • v1.17.0
  • v1.18.0
  • v1.2.0
  • v1.3.0
  • v1.3.1
  • v1.3.2
  • v1.3.3
  • v1.4.0
  • v1.5.0
  • v1.6.0
  • v1.7.0
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.8.3
  • v1.8.4
  • v1.9.0
  • v1.9.1
  • v1.9.2
  • v1.9.3
  • v2.0.0
  • v2.1.0
  • v3.0.0
  • v3.0.1
  • v3.0.10
  • v3.0.11
  • v3.0.12
  • v3.0.2
  • v3.0.3
  • v3.0.4
  • v3.0.5
  • v3.0.6
  • v3.0.7
  • v3.0.8
  • v3.0.9
  • v3.1.0
  • v3.1.1
  • v3.1.2
  • v3.1.3
  • v3.1.4
  • v3.2.0
  • v3.2.1
  • v3.2.2
  • v3.3.0
  • v3.4.0
  • v3.5.0
  • v3.5.1
  • v3.5.2
  • v3.5.3
  • v3.5.4
  • v3.5.5
  • v3.5.6
  • v3.5.7
  • v3.6.0
  • v3.6.1
  • v3.6.2
  • v3.6.3
  • v4.0.0
  • v4.0.1
  • v4.0.2
  • v4.0.3
  • v4.0.4
  • v4.0.5
175 results
Show changes
Commits on Source (4)
Showing
with 1142 additions and 1444 deletions
module.exports = {
root: true,
env: {
node: true,
es2021: true,
},
ignorePatterns: ["node_modules", "build", "coverage"],
plugins: ["eslint-comments", "functional"],
extends: [
"plugin:vue/essential",
"plugin:vue/recommended",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
......@@ -14,17 +14,14 @@ module.exports = {
],
parserOptions: {
ecmaVersion: 2020,
parser: "@typescript-eslint/parser",
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"eslint-comments/disable-enable-pair": [
"error",
{ "allowWholeFile": true }
],
"@typescript-eslint/no-empty-interface": 1, // empty Interfaces will be only warnings for now.
"vue/multi-word-component-names": "off"
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
}
};
......@@ -8,9 +8,6 @@ packageExtensions:
"vue-material-design-icons@*":
dependencies:
"vue": "^2.6.14"
"vuelidate@*":
dependencies:
"vue": "^2.6.14"
"bootstrap-vue@*":
dependencies:
"vue": "^2.6.14"
......
# FormGenerator
# SHACL Form Generator
This repository contains the CoScInE form generation vue component.
This repository contains the [SHACL](https://www.w3.org/TR/shacl/) form-generator [Vue](https://vuejs.org/) component used in Coscine.
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
### Usage
```html
<FormGenerator
:disabled-mode="formDisabled"
:fixed-value-mode="fixedValueMode"
:fixed-values="fixedValues"
:form-data="metadata"
:form-data-mime-type="metadataMimeType"
:locale="locale"
:selected-shape="applicationProfileUrl"
:shape-mime-type="shapeMimeType"
:shapes="applicationProfile"
:class-receiver="implementedReceiveClass"
:user-receiver="implementedReceiveUser"
@input="inputMetadata"
@inputFixedValues="inputFixedValues"
/>
```
{
"name": "@coscine/form-generator",
"version": "1.18.0",
"version": "2.0.0",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"browser": "dist/index.umd.js",
"browser": "dist/index.es.js",
"style": "dist/style.css",
"types": "dist/index.d.ts",
"directories": {
"doc": "docs"
},
......@@ -22,9 +23,10 @@
},
"dependencies": {
"@coscine/api-client": "^1.5.0",
"@zazuko/rdf-vocabularies": "^2021.9.22-3",
"bootstrap-vue": "^2.20.1",
"rdf-ext": "^1.3.4",
"rdf-parse": "1.8.0",
"rdf-ext": "^2.0.1",
"rdf-parse": "^1.8.0",
"rdf-validate-shacl": "^0.2.5",
"stream-browserify": "^3.0.0",
"uuid": "^8.3.2",
......@@ -32,21 +34,21 @@
"vue-i18n": "^8.22.2",
"vue-material-design-icons": "^4.11.0",
"vue-multiselect": "^2.1.6",
"vue-runtime-helpers": "^1.1.2",
"vuelidate": "^0.7.6"
"vue-runtime-helpers": "^1.1.2"
},
"devDependencies": {
"@hutson/semantic-delivery-gitlab": "^9.1.0",
"@rollup/plugin-replace": "^4.0.0",
"@semantic-release/commit-analyzer": "^8.0.1",
"@semantic-release/git": "^9.0.0",
"@semantic-release/gitlab": "^6.0.5",
"@semantic-release/npm": "^7.0.6",
"@semantic-release/release-notes-generator": "^9.0.1",
"@types/node": "^14.14.20",
"@types/rdf-ext": "^1.3.11",
"@types/rdf-js": "^4.0.0",
"@types/rdf-validate-shacl": "^0.2.4",
"@types/uuid": "^8.3.1",
"@types/vuelidate": "^0.7.13",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"@vue/cli-plugin-eslint": "^4.5.15",
......@@ -76,5 +78,5 @@
"url": "https://git.rwth-aachen.de/coscine/frontend/libraries/form-generator.git"
},
"license": "MIT",
"packageManager": "yarn@3.2.0"
"packageManager": "yarn@3.2.1"
}
<template>
<div id="FormGenerator">
<div v-for="(formElement, index) in sortedSHACLDefinition" :key="index">
<div v-for="(pathToProperty, index) in pathList" :key="index">
<WrapperInput
:componentDefinition="fieldDefinition(formElement)"
:formFieldInformation="formElement"
:formData="formData"
:fixedValueMode="fixedValueMode"
:fixedValues="fixedValues"
:disabledMode="disabledMode"
:languageLocale="languageLocale"
:v="$v"
:errorMessages="errorMessages"
:classReceiver="classReceiver"
:userReceiver="userReceiver"
:dataset="dataset"
:property="pathToProperty.subject"
:metadata="metadata"
:fixed-value-mode="fixedValueMode"
:fixed-values="internalFixedValues"
:disabled-mode="disabledMode"
:language-locale="locale"
:error-messages="errorMessages"
:class-receiver="classReceiver"
:user-receiver="userReceiver"
@input="input"
@triggerValidation="
validateMetadata(formData, quads, applicationProfileId)
"
@inputFixedValues="inputFixedValues"
@triggerValidation="triggerMetadataValidation(cleanedMetadata, dataset)"
/>
</div>
</div>
......@@ -28,20 +26,26 @@ import Vue, { PropType } from 'vue';
import locale from '@/locale';
import VueI18n from 'vue-i18n';
import Vuelidate from 'vuelidate';
import { MetadataApi, UserApi } from '@coscine/api-client';
import { Quad } from 'rdf-js';
import FieldReader from './util/FieldReader';
import WrapperInput from './components/WrapperInput.vue';
import LinkedDataHandler from './base/LinkedDataHandler';
import type { Dataset, Quad, Quad_Object } from 'rdf-js';
import factory from 'rdf-ext';
import WrapperInput from '@/components/WrapperInput.vue';
import LinkedDataHandler from '@/base/LinkedDataHandler';
import { prefixes } from '@zazuko/rdf-vocabularies';
import type { UserObject } from '@coscine/api-client/dist/types/Coscine.Api.User';
import type { BilingualLabels } from '@coscine/api-client/dist/types/Coscine.Api.Metadata';
import type {
FixedValueObject,
FixedValues,
ValueType,
} from '@/types/fixedValues';
import DatasetExt from 'rdf-ext/lib/Dataset';
Vue.use(VueI18n);
Vue.use(Vuelidate);
const i18n = new VueI18n({
locale: 'en',
......@@ -56,40 +60,44 @@ export default LinkedDataHandler.extend({
WrapperInput,
},
props: {
applicationProfileId: {
default: '',
type: String,
formData: {
default: () => factory.dataset() as unknown as Dataset,
type: [String, Object] as PropType<string | Dataset>,
},
mimeType: {
formDataMimeType: {
default: 'application/ld+json',
type: String,
},
formData: {
default: () => ({}),
type: Object,
},
fixedValueMode: {
disabledMode: {
default: false,
type: Boolean,
},
disabledMode: {
fixedValueMode: {
default: false,
type: Boolean,
},
fixedValues: {
default: () => ({}),
type: Object,
type: Object as PropType<FixedValues>,
},
SHACLDefinition: {
selectedShape: {
default: 'https://purl.org/coscine/ap/',
type: String,
},
shapes: {
default: '',
type: [String, Object, null] as PropType<string | Dataset | null>,
},
shapesMimeType: {
default: 'application/ld+json',
type: String,
},
languageLocale: {
locale: {
default: 'en',
type: String,
},
classReceiver: {
default: async (classUrl: string) => {
default: () => async (classUrl: string) => {
const response = await MetadataApi.metadataGetClassInstances(classUrl);
return response.data;
},
......@@ -98,7 +106,7 @@ export default LinkedDataHandler.extend({
>,
},
userReceiver: {
default: async () => {
default: () => async () => {
const response = await UserApi.userGetUser();
return response.data;
},
......@@ -107,198 +115,245 @@ export default LinkedDataHandler.extend({
},
data() {
return {
properties: [] as Array<Quad>,
quads: [] as Array<Quad>,
sortedSHACLDefinition: [] as any[],
errorMessages: {} as any,
timeouts: {} as any,
timeoutInterval: 500,
validations: {} as any,
cleanedMetadata: factory.dataset() as unknown as Dataset,
dataset: factory.dataset() as unknown as Dataset,
internalFixedValues: {} as FixedValues,
metadata: factory.dataset() as unknown as Dataset,
};
},
validations(): any {
if (!this.fixedValueMode) {
return {
formData: this.validations,
};
computed: {
orders(): Record<string, number> {
return Array.from(
this.dataset.match(null, factory.namedNode(prefixes.sh + 'order'))
).reduce((result, item) => {
result[item.subject.value] = parseInt(item.object.value);
if (isNaN(result[item.subject.value]))
result[item.subject.value] = Number.MAX_VALUE;
return result;
}, {} as Record<string, number>);
},
pathList(): Quad[] {
return Array.from(
this.dataset.match(null, factory.namedNode(prefixes.sh + 'path'))
)
.filter((entry) => entry.object.value !== prefixes.rdf + 'type')
.sort((entry1, entry2) => this.compareEntries(entry1, entry2));
},
targetClass(): Quad_Object {
for (const targetClass of this.dataset.match(
null,
factory.namedNode(prefixes.sh + 'targetClass')
)) {
return targetClass.object;
}
return {
formData: {},
};
},
beforeMount() {
i18n.locale = this.languageLocale;
this.handleApplicationProfiles();
},
watch: {
formData() {
this.input();
this.createValidations();
this.$v.$reset();
this.validateMetadata(
this.formData,
this.quads,
this.applicationProfileId
return factory.namedNode(
this.selectedShape ? this.selectedShape : 'https://purl.org/coscine/ap/'
);
},
languageLocale() {
i18n.locale = this.languageLocale;
},
SHACLDefinition() {
this.handleApplicationProfiles();
async beforeMount() {
i18n.locale = this.locale;
await this.parseFormData();
this.metadata = this.cleanedMetadata;
this.internalFixedValues = JSON.parse(JSON.stringify(this.fixedValues));
await this.handleApplicationProfiles();
this.initMetadata();
},
watch: {
fixedValues() {
this.internalFixedValues = JSON.parse(JSON.stringify(this.fixedValues));
},
methods: {
createValidations() {
const validator = {} as any;
for (const nodename of Object.keys(this.formData)) {
validator[nodename] = {
$each: {
value: {
shaclValidated: async () => {
this.input();
// A debounce has been implemented based on the nodename
if (this.timeouts[nodename])
clearTimeout(this.timeouts[nodename]);
return await new Promise((resolve) => {
this.timeouts[nodename] = setTimeout(async () => {
// Validate the whole data and check if for the current nodename an error is logged
const report = await this.validateMetadata(
this.formData,
this.quads,
this.applicationProfileId
);
let resolveValue = true;
const result = report.results.find(
async formData() {
await this.parseFormData();
// Don't replace metadata if cleanedMetadata only contains the same values without the "" ones
if (
!this.cleanedMetadata.every((entry) =>
this.metadata.some(
(newEntry) =>
newEntry.predicate.value === entry.predicate.value &&
newEntry.object.value === entry.object.value
)
) ||
this.metadata.some(
(entry) =>
entry.path !== null && entry.path.value === nodename
);
if (result !== undefined) {
this.errorMessages[nodename] = result.message;
resolveValue = false;
entry.object.value !== '' &&
!this.cleanedMetadata.some(
(newEntry) =>
newEntry.predicate.value === entry.predicate.value &&
newEntry.object.value === entry.object.value
)
)
) {
this.metadata = this.cleanedMetadata;
}
resolve(resolveValue);
}, this.timeoutInterval);
});
this.initMetadata();
this.triggerMetadataValidation(this.cleanedMetadata, this.dataset);
},
locale() {
i18n.locale = this.locale;
},
shapes() {
this.handleApplicationProfiles();
this.initMetadata();
},
};
targetClass() {
this.initMetadata();
},
},
methods: {
compareEntries(entry1: Quad, entry2: Quad) {
let entry1Order: number = Number.MAX_VALUE;
let entry2Order: number = Number.MAX_VALUE;
if (entry1.subject.value in this.orders) {
entry1Order = this.orders[entry1.subject.value];
}
if (entry2.subject.value in this.orders) {
entry2Order = this.orders[entry2.subject.value];
}
if (entry1Order === entry2Order) {
return 0;
}
this.validations = validator;
return entry1Order > entry2Order ? 1 : -1;
},
initMetadata() {
this.input(prefixes.rdf + 'type', [this.targetClass]);
},
async handleApplicationProfiles() {
this.quads = await this.retrieveQuads(
this.SHACLDefinition,
this.mimeType,
this.applicationProfileId
);
this.properties = this.retrieveProperties(
this.SHACLDefinition,
this.quads,
this.applicationProfileId
if (typeof this.shapes === 'string') {
this.dataset = await this.retrieveDataset(
this.shapes,
this.shapesMimeType,
this.selectedShape
);
const unmappedSubjects = this.retrievePropertyOrder(this.properties);
this.setPropertySettings(this.properties);
this.setSHACLDefinition(unmappedSubjects);
this.createValidations();
await this.validateMetadata(
this.formData,
this.quads,
this.applicationProfileId
);
},
input() {
this.$emit('input', this.formData);
},
setPropertySettings(properties: Array<Quad>) {
const propertySubjects = this.getPropertySubjects(properties);
this.errorMessages = {} as any;
this.timeouts = {} as any;
for (const propertySubject of propertySubjects) {
this.errorMessages[propertySubject] = '';
this.timeouts[propertySubject] = null;
} else if (this.shapes !== null) {
this.dataset = factory.dataset(
Array.from(this.shapes)
) as unknown as Dataset;
}
this.triggerMetadataValidation(this.cleanedMetadata, this.dataset);
},
setSHACLDefinition(unmappedSubjects: any) {
this.sortedSHACLDefinition = [];
let keys = Object.keys(unmappedSubjects).sort(
(a, b) => parseInt(a, 10) - parseInt(b, 10)
input(nodeName: string, values: Quad_Object[]) {
const subjectNode = this.getMetadataSubject(this.metadata);
const node = factory.namedNode(nodeName);
const oldValues = Array.from(this.metadata.match(undefined, node));
if (
oldValues.length !== values.length ||
!oldValues.every((entry) =>
values.some((newEntry) => newEntry.value === entry.object.value)
) ||
!values.every((entry) =>
oldValues.some((newEntry) => newEntry.object.value === entry.value)
)
) {
this.metadata.deleteMatches(undefined, node);
this.metadata.addAll(
values.map((value) => factory.quad(subjectNode, node, value))
);
// Reactivity Trigger
this.metadata = (
this.metadata as unknown as DatasetExt
).clone() as unknown as Dataset;
const newCleanedMetadata = this.metadata.filter(
(entry) => entry.object.value !== ''
);
for (let i = 0; i < keys.length; i++) {
this.sortedSHACLDefinition.push(unmappedSubjects[keys[i]]);
const nodeName = FieldReader.getNodeName(unmappedSubjects[keys[i]]);
const minCount = FieldReader.getMinCount(unmappedSubjects[keys[i]], 1);
// if formData is empty intialize it with an empty value
if (!FieldReader.isDataValueAssigned(this.formData, nodeName)) {
this.$set(this.formData, nodeName, []);
for (let index = 0; index < minCount; index++) {
this.formData[nodeName].push({});
this.$set(this.formData[nodeName][index], 'value', '');
}
} else {
for (let index = 0; index < minCount; index++) {
if (
!FieldReader.isDataValueAssignedToKey(
this.formData,
nodeName,
'value',
index
!newCleanedMetadata.every((entry) =>
this.cleanedMetadata.some(
(oldEntry) =>
entry.predicate.value === oldEntry.predicate.value &&
entry.object.value === oldEntry.object.value
)
) ||
!this.cleanedMetadata.every((entry) =>
newCleanedMetadata.some(
(oldEntry) =>
entry.predicate.value === oldEntry.predicate.value &&
entry.object.value === oldEntry.object.value
)
)
) {
this.formData[nodeName][index] = { value: '' };
}
}
this.cleanedMetadata = newCleanedMetadata;
this.$emit('input', this.cleanedMetadata);
this.triggerMetadataValidation(this.cleanedMetadata, this.dataset);
}
}
},
checkField(data: any, nodename: string, property = 'value') {
return FieldReader.isValueAssignedToKey(data, nodename, property);
},
fieldDefinition(formElement: any) {
if (this.checkField(formElement, 'http://www.w3.org/ns/shacl#datatype')) {
const datatype = FieldReader.getObject(
formElement,
'http://www.w3.org/ns/shacl#datatype'
);
if (datatype['value'] === 'http://www.w3.org/2001/XMLSchema#string') {
inputFixedValues(nodeName: string, object: FixedValueObject) {
let different = false;
if (this.internalFixedValues[nodeName]) {
const previousObject = this.internalFixedValues[nodeName];
if (
previousObject['https://purl.org/coscine/defaultValue']?.length ===
object['https://purl.org/coscine/defaultValue']?.length &&
previousObject['https://purl.org/coscine/invisible']?.length ===
object['https://purl.org/coscine/invisible']?.length &&
previousObject['https://purl.org/coscine/fixedValue']?.length ===
object['https://purl.org/coscine/fixedValue']?.length
) {
// If some property is different between the old and new object
if (
this.checkField(
formElement,
'http://datashapes.org/dash#singleLine'
!(
this.checkPropertyExistsInOtherList(
previousObject['https://purl.org/coscine/defaultValue'],
object['https://purl.org/coscine/defaultValue']
) &&
this.checkPropertyExistsInOtherList(
object['https://purl.org/coscine/defaultValue'],
previousObject['https://purl.org/coscine/defaultValue']
) &&
this.checkPropertyExistsInOtherList(
previousObject['https://purl.org/coscine/invisible'],
object['https://purl.org/coscine/invisible']
) &&
FieldReader.getObject(
formElement,
'http://datashapes.org/dash#singleLine'
)['value'] === 'false'
this.checkPropertyExistsInOtherList(
object['https://purl.org/coscine/invisible'],
previousObject['https://purl.org/coscine/invisible']
) &&
this.checkPropertyExistsInOtherList(
previousObject['https://purl.org/coscine/fixedValue'],
object['https://purl.org/coscine/fixedValue']
) &&
this.checkPropertyExistsInOtherList(
object['https://purl.org/coscine/fixedValue'],
previousObject['https://purl.org/coscine/fixedValue']
)
)
) {
return 'InputTextArea';
} else {
return 'InputTextField';
different = true;
}
} else if (
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#date'
) {
return 'InputDatePicker';
} else if (
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#boolean'
) {
return 'InputBooleanCombobox';
} else {
different = true;
}
return 'InputTextField';
} else {
if (this.checkField(formElement, 'http://www.w3.org/ns/shacl#class')) {
return 'InputCombobox';
different = true;
}
if (this.checkField(formElement, 'http://www.w3.org/ns/shacl#in')) {
return 'InputTextField';
this.internalFixedValues[nodeName] = object;
if (different) {
this.$emit('inputFixedValues', this.internalFixedValues);
}
},
checkPropertyExistsInOtherList(first?: ValueType[], second?: ValueType[]) {
return (
!first ||
first.every(
(entry) =>
second && second.some((newEntry) => entry.value === newEntry.value)
)
);
},
async parseFormData() {
if (typeof this.formData === 'string') {
this.cleanedMetadata = await this.retrieveDataset(
this.formData,
this.formDataMimeType,
this.selectedShape
);
} else {
this.cleanedMetadata = factory.dataset(
Array.from(this.formData)
) as unknown as Dataset;
}
return null;
},
},
});
......
......@@ -4,245 +4,148 @@ import factory from 'rdf-ext';
import SHACLValidator from 'rdf-validate-shacl';
import rdfParser from 'rdf-parse';
import { Readable } from 'stream';
import { DataFactory, DatasetCore, DatasetCoreFactory, Quad } from 'rdf-js';
import ValidationReport from 'rdf-validate-shacl/src/validation-report';
import { prefixes } from '@zazuko/rdf-vocabularies';
import type { Dataset, Quad, Quad_Object, Quad_Subject } from 'rdf-js';
export default Vue.extend({
data() {
return {
shapes: [] as string[],
errorMessages: {} as { [nodeName: string]: Quad_Object[] },
timeOutId: 0,
validationDebounce: 500,
};
},
methods: {
async validateMetadata(
formData: any,
quads: Array<Quad>,
applicationProfileId: string
): Promise<
ValidationReport<
DataFactory<Quad, Quad> &
DatasetCoreFactory<Quad, Quad, DatasetCore<Quad, Quad>>
>
> {
// RDF/JSON => JSON-LD since the loadDataset function doesn't support RDF/JSON
const combinedDataObject = [];
let targetClass: string | undefined = undefined;
const targetClassDefs = quads.filter(
(quad) =>
quad.subject.value === applicationProfileId &&
quad.predicate.value === 'http://www.w3.org/ns/shacl#targetClass'
);
if (targetClassDefs.length > 0) {
targetClass = targetClassDefs[0].object.value;
getMetadataSubject(metadata: Dataset): Quad_Subject {
let subjectNode: Quad_Subject = factory.blankNode();
const rdfType = factory.namedNode(prefixes.rdf + 'type');
if (metadata.size) {
for (const entry of metadata.match(undefined, rdfType)) {
subjectNode = entry.subject;
break;
}
const dataObject = this.processDataObject(
formData,
targetClass ? targetClass : applicationProfileId
);
combinedDataObject.push(dataObject);
}
return subjectNode;
},
triggerMetadataValidation(metadata: Dataset, dataset: Dataset): void {
if (this.timeOutId) {
clearTimeout(this.timeOutId);
}
this.timeOutId = setTimeout(async () => {
const combinedMetadata = factory.dataset(Array.from(metadata));
// rdfs:subClassOf definitions have to be included for the validation to work with inheritance
const subClasses = quads.filter(
(quad) =>
quad.predicate.value ===
'http://www.w3.org/2000/01/rdf-schema#subClassOf'
const subClasses = dataset.match(
null,
factory.namedNode(prefixes.rdfs + 'subClassOf')
);
for (const subClassQuad of subClasses) {
combinedDataObject.push({
'@id': subClassQuad.subject.value,
'http://www.w3.org/2000/01/rdf-schema#subClassOf': [
{
'@id': subClassQuad.object.value,
},
],
});
combinedMetadata.add(subClassQuad);
}
// The non-referenced shapes have also to be represented
const nonReferenced = subClasses.filter(
(quad) =>
!subClasses.some(
(subQuad) => subQuad.subject.value === quad.object.value
)
// Describe that the selected value for a class relationship is a value of that class to satisfy validation
const pathRelations = dataset.match(
null,
factory.namedNode(prefixes.sh + 'path')
);
for (const nonReference of nonReferenced) {
combinedDataObject.push({
'@id': nonReference.object.value,
const classRelations = Array.from(pathRelations).map((pathRelation) => {
return {
path: pathRelation.object,
classRelations: dataset.match(
pathRelation.subject,
factory.namedNode(prefixes.sh + 'class')
),
};
});
for (const classRelation of classRelations.filter(
(entry) => entry.classRelations && entry.classRelations.size
)) {
const metadataRelations = metadata.match(
undefined,
classRelation.path
);
if (metadataRelations.size) {
for (const metadataRelation of metadataRelations) {
for (const classEntry of classRelation.classRelations) {
combinedMetadata.add(
factory.quad(
metadataRelation.object as Quad_Subject,
factory.namedNode(prefixes.rdf + 'type'),
classEntry.object
)
);
}
}
}
}
const data = await this.loadDataset(
JSON.stringify(combinedDataObject),
'application/ld+json',
undefined
);
const validator = new SHACLValidator(quads as any);
const report = validator.validate(data);
const validator = new SHACLValidator(dataset);
const report = validator.validate(combinedMetadata);
this.$emit('isValid', report);
this.errorMessages = {};
for (const result of report.results) {
if (result.path) {
const path = result.path;
if (
metadata.some(
(entry) =>
entry.predicate.value === path.value &&
entry.object.value !== ''
)
) {
this.errorMessages[path.value] = result.message;
}
}
}
return report;
}, this.validationDebounce) as unknown as number;
},
async getQuads(
data: string,
mimeType: string,
baseUri: string | undefined
): Promise<Array<Quad>> {
): Promise<Dataset> {
const input = new Readable({
read: () => {
input.push(data);
input.push(null);
},
});
const quads = [] as Array<Quad>;
const dataset = factory.dataset();
return new Promise((resolve) => {
rdfParser
.parse(input, { contentType: mimeType, baseIRI: baseUri })
.on('data', (quad: Quad) => {
quads.push(quad);
dataset.add(quad);
})
.on('end', () => resolve(quads));
.on('end', () => resolve(dataset as unknown as Dataset));
});
},
async loadDataset(
data: string,
mimeType: string,
baseUri: string | undefined
) {
): Promise<Dataset> {
const quads = await this.getQuads(data, mimeType, baseUri);
const dataSet = factory.dataset();
for (const quad of quads) {
dataSet.add(quad);
}
return dataSet;
},
fillShapesList(quads: Array<Quad>, currentShape: string) {
this.shapes = [];
this.fillShapesListRecursive(quads, this.shapes, currentShape);
},
fillShapesListRecursive(
quads: Array<Quad>,
shapes: Array<string>,
currentShape: string
) {
if (!shapes.includes(currentShape)) {
shapes.push(currentShape);
const subClasses = quads
.filter(
(quad) =>
quad.predicate.value ===
'http://www.w3.org/2000/01/rdf-schema#subClassOf' &&
quad.subject.value === currentShape
)
.map((quad) => quad.object.value);
for (const subClass of subClasses) {
this.fillShapesListRecursive(quads, shapes, subClass);
}
}
return dataSet as unknown as Dataset;
},
async retrieveQuads(
SHACLDefinition: string,
async retrieveDataset(
shapes: string,
mimeType: string,
applicationProfileId: string
): Promise<Array<Quad>> {
if (SHACLDefinition !== '') {
return this.getQuads(SHACLDefinition, mimeType, applicationProfileId);
}
return [];
},
retrieveProperties(
SHACLDefinition: string,
quads: Array<Quad>,
applicationProfileId: string
): Array<Quad> {
if (SHACLDefinition !== '') {
this.fillShapesList(quads, applicationProfileId);
const propertySets = quads
.filter(
(quad) =>
quad.predicate.value === 'http://www.w3.org/ns/shacl#property' &&
this.shapes.includes(quad.subject.value)
)
.map((quad) => quad.object.value);
return quads.filter((quad) =>
propertySets.includes(quad.subject.value)
);
}
return [];
},
retrievePropertyOrder(properties: Array<Quad>) {
const propertySubjects = this.getPropertySubjects(properties);
let highestOrder = Math.max(
...properties
.filter(
(property) =>
property.predicate.value === 'http://www.w3.org/ns/shacl#order'
)
.map((property) => Number(property.object.value))
selectedShape: string
): Promise<Dataset> {
if (shapes !== '') {
return await this.getQuads(
shapes,
mimeType,
selectedShape ? selectedShape : 'https://purl.org/coscine/ap/'
);
if (highestOrder === -Infinity) {
highestOrder = 0;
}
const unmappedSubjects = {} as any;
let manualOrder = highestOrder + 1;
for (const propertySubject of propertySubjects) {
let currentOrder = 0;
const orderDefinitions = properties.filter(
(property) =>
property.subject.value === propertySubject &&
property.predicate.value === 'http://www.w3.org/ns/shacl#order'
);
if (orderDefinitions.length > 0) {
currentOrder = Number(orderDefinitions[0].object.value);
} else {
currentOrder = manualOrder;
manualOrder++;
}
unmappedSubjects[currentOrder] = properties.filter(
(property) => property.subject.value === propertySubject
);
}
return unmappedSubjects;
},
getPropertySubjects(properties: Array<Quad>) {
return [...new Set(properties.map((property) => property.subject.value))];
},
processDataObject(formData: any, typeIdentifier: string) {
const dataObject = {} as any;
dataObject['@type'] = typeIdentifier;
for (const nodeName of Object.keys(formData)) {
if (
formData[nodeName].some((element: any) => element['value'] !== '')
) {
dataObject[nodeName] = [];
for (const metadataEntry of formData[nodeName]) {
// skip empty and repeating values for ['@id'] or ['@value']
if (metadataEntry['value'] !== '') {
if (
metadataEntry['type'] === 'uri' &&
!dataObject[nodeName].some(
(element: any) => element['@id'] === metadataEntry['value']
)
) {
dataObject[nodeName].push({
'@id': metadataEntry['value'],
'@type': metadataEntry['datatype'],
});
} else if (
!dataObject[nodeName].some(
(element: any) => element['@value'] === metadataEntry['value']
)
) {
dataObject[nodeName].push({
'@value': metadataEntry['value'],
'@type': metadataEntry['datatype'],
});
}
}
}
}
}
return dataObject;
return factory.dataset() as unknown as Dataset;
},
},
});
<template>
<multiselect
:id="cssId"
:options="selectableOptions"
<MultiSelect
v-model="selectedOption"
:options="selectableOptions"
label="name"
track-by="name"
:placeholder="$t('selectPlaceholder')"
:required="required"
:disabled="disabledMode || locked"
@input="input"
select-label=""
selected-label=""
deselect-label=""
@input="input"
/>
</template>
<script lang="ts">
import Vue from 'vue';
import Vue, { PropType } from 'vue';
import VueI18n from 'vue-i18n';
import locale from '@/locale';
import FieldReader from '@/util/FieldReader';
import Multiselect from 'vue-multiselect';
import 'vue-multiselect/dist/vue-multiselect.min.css';
Vue.component('multiselect', Multiselect);
import type { NamedNode, Quad_Object } from 'rdf-js';
import factory from 'rdf-ext';
import { prefixes } from '@zazuko/rdf-vocabularies';
import type { Label } from '@coscine/api-client/dist/types/Coscine.Api.Metadata';
Vue.component('MultiSelect', Multiselect);
Vue.use(VueI18n);
......@@ -37,84 +39,89 @@ const i18n = new VueI18n({
export default Vue.extend({
i18n,
name: 'InputBooleanCombobox',
beforeMount() {
i18n.locale = this.languageCode;
// set the datatype
this.$set(this.formData[this.nodeName][this.entryKey], 'type', 'literal');
this.$set(
this.formData[this.nodeName][this.entryKey],
'datatype',
'http://www.w3.org/2001/XMLSchema#boolean'
);
if (this.fixedValueMode) {
this.updateFixedValues(this.entryKey);
}
props: {
locked: {
default: false,
type: Boolean,
},
mounted() {
if (this.languageCode === 'en') {
this.selectableOptions = [
{ value: 'true', name: 'Yes' },
{ value: 'false', name: 'No' },
];
} else {
this.selectableOptions = [
required: {
default: false,
type: Boolean,
},
languageCode: {
default: 'en',
type: String,
},
entry: {
required: true,
type: Object as PropType<Quad_Object>,
},
dataType: {
default: () => factory.namedNode(prefixes.xsd + 'boolean'),
type: Object as PropType<NamedNode<string>>,
},
disabledMode: {
default: false,
type: Boolean,
},
},
data() {
return {
object: factory.literal('', this.dataType),
};
},
computed: {
selectableOptions(): Label[] {
if (this.languageCode === 'de') {
return [
{ value: 'true', name: 'Ja' },
{ value: 'false', name: 'Nein' },
];
}
this.loadData();
if (this.fixedValueMode) {
this.updateFixedValues(this.entryKey);
}
return [
{ value: 'true', name: 'Yes' },
{ value: 'false', name: 'No' },
];
},
data() {
selectedOption(): Label | undefined {
if (this.object.value !== '') {
const foundOption = this.selectableOptions.find(
(option) => option.value === this.object.value
);
return {
selectableOptions: [] as Record<string, unknown>[],
selectedOption: {} as Record<string, unknown>,
name: foundOption?.name,
value: this.object.value,
};
}
return undefined;
},
},
watch: {
entry() {
this.loadData();
},
methods: {
input() {
FieldReader.setField(
this.formData,
this.v,
this.nodeName,
this.selectedOption !== null ? (this.selectedOption as any).value : '',
this.$set,
this.entryKey
);
this.updateFixedValues(this.entryKey);
},
beforeMount() {
i18n.locale = this.languageCode;
},
mounted() {
this.loadData();
},
methods: {
loadData() {
// unnecessary logic for :multiselect="true"
const selectedData = [this.formData[this.nodeName][this.entryKey]];
for (const field of selectedData) {
const selectedElement = this.selectableOptions.filter((obj: any) => {
return obj.value === (field as any).value;
});
if (this.object.value !== this.entry.value) {
this.object.value = this.entry.value;
}
},
input(label: Label) {
if (label.value) {
this.object.value = label.value;
} else {
this.object.value = '';
}
this.$emit('triggerValidation');
this.$emit('input', this.object);
},
},
props: {
cssId: String,
invisible: Boolean,
locked: Boolean,
required: Boolean,
nodeName: String,
formFieldInformation: Array,
formData: Object,
fixedValueMode: Boolean,
disabledMode: Boolean,
checkField: Function,
updateFixedValues: Function,
languageCode: String,
v: Object,
entryKey: Number,
},
components: {},
});
</script>
......
<template>
<multiselect
:id="cssId"
<MultiSelect
:value="selectedOption"
:options="selectableOptions"
v-model="selectedOptions"
label="name"
track-by="name"
:placeholder="$t('selectPlaceholder')"
:required="required"
:disabled="disabledMode || locked"
@input="input"
select-label=""
selected-label=""
deselect-label=""
@input="input"
/>
</template>
......@@ -23,14 +22,14 @@ import locale from '@/locale';
import Multiselect from 'vue-multiselect';
import 'vue-multiselect/dist/vue-multiselect.min.css';
Vue.component('multiselect', Multiselect);
import FieldReader from '@/util/FieldReader';
Vue.component('MultiSelect', Multiselect);
import type {
BilingualLabels,
Label,
} from '@coscine/api-client/dist/types/Coscine.Api.Metadata';
import type { NamedNode, Quad_Object } from 'rdf-js';
import factory from 'rdf-ext';
Vue.use(VueI18n);
......@@ -43,82 +42,88 @@ const i18n = new VueI18n({
export default Vue.extend({
i18n,
name: 'InputCombobox',
beforeMount() {
i18n.locale = this.languageCode;
this.class = FieldReader.getObject(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#class'
).value;
// set the datatype
this.$set(this.formData[this.nodeName][this.entryKey], 'type', 'uri');
this.$set(
this.formData[this.nodeName][this.entryKey],
'datatype',
this.class
);
if (this.fixedValueMode) {
this.updateFixedValues(this.entryKey);
}
props: {
locked: {
default: false,
type: Boolean,
},
required: {
default: false,
type: Boolean,
},
entry: {
required: true,
type: Object as PropType<Quad_Object>,
},
disabledMode: {
default: false,
type: Boolean,
},
languageCode: {
default: 'en',
type: String,
},
classObject: {
required: true,
type: Object as PropType<NamedNode<string>>,
},
classReceiver: {
default: () => {
return async () => ({} as BilingualLabels);
},
type: Function as PropType<
(classUrl: string) => Promise<BilingualLabels>
>,
},
mounted() {
this.retrieveLabels();
if (this.fixedValueMode) {
this.updateFixedValues(this.entryKey);
}
},
data() {
return {
class: '',
selectableOptions: [] as Label[],
selectedOptions: [] as Label[],
maxNumberOfSelectedObjects: 10000,
object: factory.namedNode<string>('') as NamedNode<string>,
};
},
methods: {
input() {
// Unnecessary logic for :multiselect="true". Not removed for future reference.
if (Array.isArray(this.selectedOptions)) {
const array = [];
for (const field of this.selectedOptions) {
array.push({
value: (field as any).value,
type: 'uri',
datatype: this.class,
});
computed: {
selectedOption(): Label | undefined {
if (this.object.value !== '') {
const foundOption = this.selectableOptions.find(
(option) => option.value === this.object.value
);
return {
name: foundOption?.name,
value: this.object.value,
};
}
this.$set(this.formData[this.nodeName], this.entryKey, array);
return undefined;
},
},
watch: {
entry() {
this.loadData();
},
},
mounted() {
i18n.locale = this.languageCode;
this.retrieveLabels();
},
methods: {
input(label: Label) {
if (label.value) {
this.object.value = label.value;
} else {
// Will always enter here, since :multiselect="false" by default
this.$set(this.formData[this.nodeName], this.entryKey, {
value: (this.selectedOptions as any).value,
type: 'uri',
datatype: this.class,
});
}
if (this.v.formData[this.nodeName]) {
this.v.formData[this.nodeName].$touch();
this.object.value = '';
}
this.updateFixedValues(this.entryKey);
this.$emit('input', this.object);
},
loadData() {
// Logic for :multiselect="true".
const selectedData = [this.formData[this.nodeName][this.entryKey]];
for (const field of selectedData) {
const selectedElement = this.selectableOptions.filter((obj: any) => {
return obj.value === (field as any).value;
});
if (selectedElement[0] !== undefined) {
this.selectedOptions.push(selectedElement[0]);
if (this.object.value !== this.entry.value) {
this.object.value = this.entry.value;
}
}
this.$emit('triggerValidation');
},
async retrieveLabels() {
try {
const bilingualLabels = await this.classReceiver(this.class);
const bilingualLabels = await this.classReceiver(
this.classObject.value
);
if (this.languageCode === 'en' && bilingualLabels.en) {
this.selectableOptions = bilingualLabels.en;
} else if (this.languageCode === 'de' && bilingualLabels.de) {
......@@ -131,33 +136,12 @@ export default Vue.extend({
this.selectableOptions = [];
}
this.loadData();
} catch {
} catch (e) {
// Method not available
console.error(e);
}
},
},
props: {
cssId: String,
invisible: Boolean,
locked: Boolean,
required: Boolean,
nodeName: String,
formFieldInformation: Array,
formData: Object,
fixedValueMode: Boolean,
disabledMode: Boolean,
checkField: Function,
updateFixedValues: Function,
languageCode: String,
v: Object,
entryKey: Number,
classReceiver: {
type: Function as PropType<
(classUrl: string) => Promise<BilingualLabels>
>,
},
},
components: {},
});
</script>
......
<template>
<b-form-datepicker
:id="cssId"
v-model="object.value"
class="formgeneratordatepicker"
v-model="selectedDate"
:locale="languageCode"
:disabled="disabledMode || locked"
@input="input"
:required="required"
calendar-width="100%"
start-weekday="1"
:state="state"
@input="input"
/>
</template>
<script lang="ts">
import Vue from 'vue';
import Vue, { PropType } from 'vue';
import { BFormDatepicker } from 'bootstrap-vue';
import FieldReader from '@/util/FieldReader';
import type { NamedNode, Quad_Object } from 'rdf-js';
import factory from 'rdf-ext';
import { prefixes } from '@zazuko/rdf-vocabularies';
export default Vue.extend({
name: 'InputDatePicker',
components: {
BFormDatepicker,
},
beforeMount() {
// take whatever is defined in formdata now and try to get the content if we are not in fixed value mode
this.replacePlaceholder();
// set the datatype
this.$set(this.formData[this.nodeName][this.entryKey], 'type', 'literal');
this.$set(
this.formData[this.nodeName][this.entryKey],
'datatype',
'http://www.w3.org/2001/XMLSchema#date'
);
if (this.fixedValueMode) {
this.input();
}
props: {
languageCode: {
default: 'en',
type: String,
},
data() {
return {
selectedDate: '',
};
locked: {
default: false,
type: Boolean,
},
required: {
default: false,
type: Boolean,
},
entry: {
required: true,
type: Object as PropType<Quad_Object>,
},
dataType: {
default: () => factory.namedNode(prefixes.xsd + 'date'),
type: Object as PropType<NamedNode<string>>,
},
disabledMode: {
default: false,
type: Boolean,
},
props: {
languageCode: String,
cssId: String,
invisible: Boolean,
locked: Boolean,
required: Boolean,
nodeName: String,
formFieldInformation: Array,
formData: Object,
fixedValueMode: Boolean,
disabledMode: Boolean,
checkDataField: Function,
checkField: Function,
updateFixedValues: Function,
v: Object,
entryKey: Number,
state: {
type: [Boolean, Object],
default: null,
},
},
data() {
return {
object: factory.literal('', this.dataType),
};
},
watch: {
entry() {
this.loadData();
},
},
mounted() {
this.loadData();
// take whatever is defined in metadata now and try to get the content if we are not in fixed value mode
this.replacePlaceholder();
},
methods: {
replacePlaceholder() {
if (this.checkDataField(this.formData, this.nodeName, this.entryKey)) {
if (
this.formData[this.nodeName][this.entryKey]['value'] === '{TODAY}'
) {
if (this.fixedValueMode) {
return;
loadData() {
if (this.object.value !== this.entry.value) {
this.object.value = this.entry.value;
}
},
replacePlaceholder() {
if (this.entry.value === '{TODAY}') {
const today = new Date();
const dd = String(today.getDate()).padStart(2, '0');
const mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
const yyyy = today.getFullYear();
this.selectedDate = yyyy + '-' + mm + '-' + dd;
this.$emit('triggerValidation');
} else {
this.selectedDate =
this.formData[this.nodeName][this.entryKey]['value'];
}
this.object.value = yyyy + '-' + mm + '-' + dd;
this.$set(
this.formData[this.nodeName][this.entryKey],
'value',
this.selectedDate
);
this.$emit('input', this.object);
this.$emit('triggerValidation');
}
},
input() {
FieldReader.setField(
this.formData,
this.v,
this.nodeName,
this.selectedDate,
this.$set,
this.entryKey
);
this.updateFixedValues(this.entryKey);
this.$emit('input', this.object);
},
},
});
</script>
<style>
.formgeneratordatepicker label {
<style scoped>
.formgeneratordatepicker >>> label {
display: flex;
align-items: center;
}
.formgeneratordatepicker div.is-valid {
.formgeneratordatepicker >>> div.is-valid {
background-image: unset;
}
.formgeneratordatepicker footer {
.formgeneratordatepicker >>> footer {
display: none;
}
.formgeneratordatepicker button {
.formgeneratordatepicker >>> button {
min-width: unset;
margin: unset;
}
.b-calendar button {
.formgeneratordatepicker >>> .b-calendar button {
min-width: unset;
}
</style>
<style scoped></style>
<template>
<b-form-textarea
:id="cssId"
v-model="textValue"
@input="input"
:disabled="disabledMode || locked"
:required="required"
:state="state"
></b-form-textarea>
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { BFormTextarea } from 'bootstrap-vue';
import FieldReader from '@/util/FieldReader';
import type { UserObject } from '@coscine/api-client/dist/types/Coscine.Api.User';
export default Vue.extend({
name: 'InputTextArea',
components: {
BFormTextarea,
},
beforeMount() {
// take whatever is defined in formdata now and try to get the content if we are not in fixed value mode
this.replacePlaceholder();
// set the datatype
this.$set(this.formData[this.nodeName][this.entryKey], 'type', 'literal');
this.$set(
this.formData[this.nodeName][this.entryKey],
'datatype',
'http://www.w3.org/2001/XMLSchema#string'
);
if (this.fixedValueMode) {
this.updateFixedValues(this.entryKey);
}
},
mounted() {
this.loadData();
},
data() {
return {
textValue: '',
};
},
props: {
cssId: String,
invisible: Boolean,
locked: Boolean,
required: Boolean,
nodeName: String,
formFieldInformation: Array,
formData: Object,
fixedValueMode: Boolean,
disabledMode: Boolean,
checkDataField: Function,
checkField: Function,
updateFixedValues: Function,
v: Object,
entryKey: Number,
state: {
type: [Boolean, Object],
default: null,
},
userReceiver: {
type: Function as PropType<() => Promise<UserObject>>,
},
},
methods: {
input() {
FieldReader.setField(
this.formData,
this.v,
this.nodeName,
this.textValue,
this.$set,
this.entryKey
);
this.updateFixedValues(this.entryKey);
},
loadData() {
this.textValue = this.formData[this.nodeName][this.entryKey]['value'];
},
replacePlaceholder() {
if (!this.fixedValueMode) {
if (this.checkDataField(this.formData, this.nodeName, this.entryKey)) {
if (this.formData[this.nodeName][this.entryKey]['value'] === '{ME}') {
this.retrieveUser();
}
}
}
},
async retrieveUser() {
try {
const user = await this.userReceiver();
this.$set(
this.formData[this.nodeName][this.entryKey],
'value',
user.displayName
);
this.textValue = this.formData[this.nodeName][this.entryKey]['value'];
this.$forceUpdate();
this.$emit('triggerValidation');
} catch {
// No user exists
}
},
},
});
</script>
<style></style>
<style scoped></style>
<template>
<b-form-input
:id="cssId"
v-model="textValue"
v-if="singleLine"
v-model="object.value"
:disabled="disabledMode || locked"
:required="required"
:state="state"
:type="type"
@input="input"
/>
<b-form-textarea
v-else
v-model="object.value"
:disabled="disabledMode || locked"
:required="required"
:state="state"
:type="type"
@input="input"
/>
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { BFormInput } from 'bootstrap-vue';
import FieldReader from '@/util/FieldReader';
import { BFormInput, BFormTextarea } from 'bootstrap-vue';
import type { UserObject } from '@coscine/api-client/dist/types/Coscine.Api.User';
import type { NamedNode, Quad_Object } from 'rdf-js';
import { prefixes } from '@zazuko/rdf-vocabularies';
import factory from 'rdf-ext';
export default Vue.extend({
name: 'InputTextField',
components: {
BFormInput,
BFormTextarea,
},
beforeMount() {
// take whatever is defined in formdata now and try to get the content if we are not in fixed value mode
this.replacePlaceholder();
const datatype = FieldReader.getObject(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#datatype'
);
// set the datatype
this.$set(this.formData[this.nodeName][this.entryKey], 'type', 'literal');
this.$set(
this.formData[this.nodeName][this.entryKey],
'datatype',
datatype['value']
);
if (datatype['value'].endsWith('#dateTime')) {
this.type = 'datetime-local';
} else if (
datatype['value'].endsWith('#byte') ||
datatype['value'].endsWith('#int') ||
datatype['value'].endsWith('#short') ||
datatype['value'].endsWith('#integer') ||
datatype['value'].endsWith('#long') ||
datatype['value'].endsWith('#unsignedByte') ||
datatype['value'].endsWith('#unsignedShort') ||
datatype['value'].endsWith('#unsignedInt') ||
datatype['value'].endsWith('#unsignedLong')
) {
this.type = 'number';
}
if (this.fixedValueMode) {
this.updateFixedValues(this.entryKey);
}
props: {
locked: {
default: false,
type: Boolean,
},
mounted() {
this.loadData();
required: {
default: false,
type: Boolean,
},
data() {
return {
textValue: '',
type: 'text',
};
singleLine: {
default: false,
type: Boolean,
},
entry: {
required: true,
type: Object as PropType<Quad_Object>,
},
dataType: {
default: () => factory.namedNode(prefixes.xsd + 'string'),
type: Object as PropType<NamedNode<string>>,
},
fixedValueMode: {
default: false,
type: Boolean,
},
disabledMode: {
default: false,
type: Boolean,
},
props: {
cssId: String,
invisible: Boolean,
locked: Boolean,
required: Boolean,
nodeName: String,
formFieldInformation: Array,
formData: Object,
fixedValueMode: Boolean,
disabledMode: Boolean,
checkDataField: Function,
checkField: Function,
updateFixedValues: Function,
v: Object,
entryKey: Number,
state: {
type: [Boolean, Object],
default: null,
},
userReceiver: {
default: () => {
return async () => ({} as UserObject);
},
type: Function as PropType<() => Promise<UserObject>>,
},
},
data() {
return {
object: factory.literal('', this.dataType),
};
},
computed: {
type(): string {
const dataType = this.dataType.value;
if (dataType.endsWith('#dateTime')) {
return 'datetime-local';
} else if (
dataType.endsWith('#byte') ||
dataType.endsWith('#int') ||
dataType.endsWith('#short') ||
dataType.endsWith('#integer') ||
dataType.endsWith('#long') ||
dataType.endsWith('#unsignedByte') ||
dataType.endsWith('#unsignedShort') ||
dataType.endsWith('#unsignedInt') ||
dataType.endsWith('#unsignedLong')
) {
return 'number';
}
return 'text';
},
},
watch: {
entry() {
this.loadData();
},
},
mounted() {
// take whatever is defined in metadata now and try to get the content if we are not in fixed value mode
this.replacePlaceholder();
this.loadData();
},
methods: {
input() {
if (this.type === 'datetime-local') {
this.textValue += ':00';
this.object.value += ':00';
}
FieldReader.setField(
this.formData,
this.v,
this.nodeName,
this.textValue,
this.$set,
this.entryKey
);
this.updateFixedValues(this.entryKey);
this.$emit('input', this.object);
},
loadData() {
this.textValue = this.formData[this.nodeName][this.entryKey]['value'];
if (this.object.value !== this.entry.value) {
this.object.value = this.entry.value;
}
},
replacePlaceholder() {
if (!this.fixedValueMode) {
if (this.checkDataField(this.formData, this.nodeName, this.entryKey)) {
if (this.formData[this.nodeName][this.entryKey]['value'] === '{ME}') {
if (this.entry.value === '{ME}') {
this.retrieveUser();
}
}
}
},
async retrieveUser() {
try {
const user = await this.userReceiver();
this.$set(
this.formData[this.nodeName][this.entryKey],
'value',
user.displayName
);
this.textValue = this.formData[this.nodeName][this.entryKey]['value'];
this.$forceUpdate();
this.$emit('triggerValidation');
} catch {
if (user.displayName) {
this.object.value = user.displayName;
this.$emit('input', this.object);
}
} catch (e) {
// No user exists
console.error(e);
}
},
},
});
</script>
<style></style>
<style scoped></style>
This diff is collapsed.
declare type CoscineType = {
authorization: {
bearer: string;
};
language: {
locale: string;
};
i18n: {
'form-generator': VueI18n.LocaleMessages | undefined;
};
};
declare const coscine: CoscineType;
declare interface Window {
coscine: CoscineType;
}
declare const _spPageContextInfo: unknown;
type FormFieldInformation = Array<any>;
......@@ -2,7 +2,7 @@ import type { VueConstructor } from 'vue';
import FormGenerator from './FormGenerator.vue';
export default {
install(Vue: VueConstructor<Vue>, options: any): void {
install(Vue: VueConstructor<Vue>): void {
Vue.component('FormGenerator', FormGenerator);
},
};
......@@ -2,6 +2,3 @@ declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
declare module 'rdf-ext';
declare module 'uuid';
export interface ValueType {
datatype?: string;
type: 'literal' | 'uri';
value: string;
}
export interface FixedValueObject {
'https://purl.org/coscine/defaultValue'?: ValueType[];
'https://purl.org/coscine/fixedValue'?: ValueType[];
'https://purl.org/coscine/invisible'?: ValueType[];
}
export interface FixedValues {
[identifier: string]: FixedValueObject;
}
export default {
getNodeName(data: any, defaultValue = '') {
if (this.isValueAssignedToKey(data, 'http://www.w3.org/ns/shacl#path')) {
return this.getObject(data, 'http://www.w3.org/ns/shacl#path').value;
}
return defaultValue;
},
getMinCount(data: any, defaultValue = -1) {
if (
this.isValueAssignedToKey(data, 'http://www.w3.org/ns/shacl#minCount')
) {
return parseInt(
this.getObject(data, 'http://www.w3.org/ns/shacl#minCount').value
);
}
return defaultValue;
},
getMaxCount(data: any, defaultValue = -1) {
if (
this.isValueAssignedToKey(data, 'http://www.w3.org/ns/shacl#maxCount')
) {
return parseInt(
this.getObject(data, 'http://www.w3.org/ns/shacl#maxCount').value
);
}
return defaultValue;
},
getMinLength(data: any, defaultValue = -1) {
if (
this.isValueAssignedToKey(data, 'http://www.w3.org/ns/shacl#minLength')
) {
return parseInt(
this.getObject(data, 'http://www.w3.org/ns/shacl#minLength').value
);
}
return defaultValue;
},
getLocalizedField(data: any, selectedLanguage: any, defaultValue = '') {
if (!this.isValueAssignedToKey(data, 'http://www.w3.org/ns/shacl#name')) {
return defaultValue;
}
for (const localizedName of this.getTriples(
data,
'http://www.w3.org/ns/shacl#name'
)) {
if (localizedName.object.language === selectedLanguage) {
return localizedName.object.value;
}
}
return defaultValue;
},
isDataValueAssigned(data: any, nodename: string | null | undefined) {
if (
data === null ||
data === undefined ||
nodename === null ||
nodename === undefined ||
data[nodename] === undefined
) {
return false;
}
return true;
},
isDataValueAssignedToKey(
data: any,
nodename: string | null | undefined,
property: string | null = 'value',
entryKey: number
) {
if (
data === null ||
data === undefined ||
nodename === null ||
nodename === undefined ||
property === null ||
data[nodename] === undefined ||
data[nodename][entryKey] === undefined ||
data[nodename][entryKey][property] === undefined ||
data[nodename][entryKey][property] === ''
) {
return false;
}
return true;
},
isFixedValueAssignedToKey(
data: any,
nodename: string | null | undefined,
property: string | null = 'value',
entryKey: number
) {
if (
data === null ||
data === undefined ||
nodename === null ||
nodename === undefined ||
property === null ||
data[nodename] === undefined ||
data[nodename][entryKey] === undefined ||
data[nodename][entryKey][property] === undefined
) {
return false;
}
return true;
},
isValueAssignedToKey(
data: any,
nodename: string | null | undefined,
property = 'value'
) {
if (
data === null ||
data === undefined ||
nodename === null ||
nodename === undefined ||
!data.some((entry: any) => entry.predicate[property] === nodename)
) {
return false;
}
return true;
},
getObject(data: any, nodename: string | null | undefined) {
if (
data === null ||
data === undefined ||
nodename === null ||
nodename === undefined
) {
return null;
}
return data.find((entry: any) => entry.predicate.value === nodename).object;
},
getAllObjects(data: any, nodename: string | null | undefined) {
if (
data === null ||
data === undefined ||
nodename === null ||
nodename === undefined
) {
return null;
}
return data
.filter((entry: any) => entry.predicate.value === nodename)
.map((entry: any) => entry.object);
},
getTriples(data: any, nodename: string | null | undefined) {
if (
data === null ||
data === undefined ||
nodename === null ||
nodename === undefined
) {
return null;
}
return data.filter((entry: any) => entry.predicate.value === nodename);
},
setField(
formData: any,
v: any,
nodeName: string,
value: any,
setFunction: (formData: any, key: string, value: any) => void,
entryKey: number
): void {
if (v.formData[nodeName]) {
v.formData[nodeName].$each[entryKey]['value'].$model = value;
} else {
setFunction(formData[nodeName][entryKey], 'value', value);
}
},
removeField(
formData: any,
v: any,
nodeName: string,
deleteFunction: (formData: any, entryKey: number) => void,
entryKey: number
): void {
deleteFunction(formData[nodeName], entryKey);
},
};
import type {
Dataset,
Literal,
Quad,
Quad_Predicate,
Quad_Subject,
} from 'rdf-js';
export function getObject(
subject: Quad_Subject,
predicate: Quad_Predicate,
dataset: Dataset,
locale?: string
): Quad[] {
let results = Array.from(dataset.match(subject, predicate));
if (locale !== undefined) {
results = results.sort((quad) =>
(quad.object as Literal).language === locale ? -1 : 1
);
if (results.length > 0) {
return [results[0]];
}
}
return results;
}
......@@ -15,7 +15,6 @@
"declaration": true,
"declarationDir": "dist",
"types": [
"webpack-env",
"node"
],
"paths": {
......
......@@ -3,6 +3,7 @@ import { defineConfig } from 'vite';
import { createVuePlugin } from "vite-plugin-vue2";
import nodePolyfills from 'rollup-plugin-polyfill-node';
import globalPolyfills from 'rollup-plugin-node-globals';
import replace from '@rollup/plugin-replace';
// https://vitejs.dev/config/
export default defineConfig({
......@@ -24,19 +25,15 @@ export default defineConfig({
},
dedupe: ["vue-demi"],
},
optimizeDeps: {
esbuildOptions: {
// Node.js global to browser globalThis
define: {
global: "globalThis",
},
},
'global': 'globalThis',
},
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: '@coscine/form-generator',
fileName: (format) => `index.${format}.js`,
formats: ['es', 'umd'],
},
rollupOptions: {
external: ['vue'],
......@@ -46,12 +43,24 @@ export default defineConfig({
globals: {
vue: 'Vue',
},
manualChunks: () => 'everything.js'
},
plugins: [
// Enable rollup polyfills plugin
// used during production bundling
nodePolyfills( /* options */ ),
globalPolyfills(),
replace({
values: {
'rdfParse.RdfParser': 'RdfParser',
"Duplex.prototype.push": "_stream_readable.prototype.push",
"_stream_readable$1": "_stream_readable",
"Readable$4.call": "Readable$3.call",
'return new Readable$3(options)': 'Object.assign(this, Readable$3.prototype, EventEmitter.prototype, Transform$2.prototype)'
},
delimiters: ['', ''],
preventAssignment: false,
})
],
},
},
......