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>
<template>
<div
v-if="componentDefinition !== null && values"
:class="componentDefinition"
v-if="componentDefinition !== null && formData[nodeName]"
:style="cssProps"
>
<b-form-group
:class="{
mandatory: valueRequired,
mandatory: required,
invisible: !fixedValueMode && invisible,
}"
label-cols-sm="3"
......@@ -14,42 +14,33 @@
:label="label"
label-class="application-profile-label"
>
<b-button-toolbar
class="my-1"
v-for="(entry, entryIndex) in formData[nodeName]"
:key="formDataIds[entryIndex]"
>
<b-button-toolbar v-for="(entry, entryIndex) in values" :key="entryIndex">
<component
class="wrapper-input"
:is="componentDefinition"
:cssId="formDataIds[entryIndex]"
:invisible="invisible"
class="wrapper-input"
:locked="locked"
:required="valueRequired"
:nodeName="nodeName"
:formFieldInformation="formFieldInformation"
:formData="formData"
:fixedValueMode="fixedValueMode"
:fixedValues="fixedValues"
:disabledMode="disabledMode"
:languageCode="languageLocale"
:checkDataField="checkDataField"
:checkField="checkField"
:updateFixedValues="updateFixedValues"
:v="v"
:entryKey="entryIndex"
:required="required"
:fixed-value-mode="fixedValueMode"
:fixed-values="fixedValues"
:disabled-mode="disabledMode"
:language-code="languageLocale"
:entry="entry"
:data-type="dataType"
:class-object="classObject"
:single-line="singleLine"
:state="state"
:classReceiver="classReceiver"
:userReceiver="userReceiver"
:class-receiver="classReceiver"
:user-receiver="userReceiver"
@input="inputValue(entryIndex, $event)"
@triggerValidation="triggerValidation"
/>
<b-button-group class="wrapper-input-button">
<b-button
v-if="fixedValueMode"
:disabled="fixedValueDisabled(entryIndex)"
:disabled="fixedValueDisabled[entryIndex]"
class="innerButton"
variant="outline-secondary"
@click.prevent="changeLockable(entryIndex)"
@click.prevent="changeLockable"
>
<LockIcon v-if="locked" />
<LockOpenIcon v-else />
......@@ -57,29 +48,29 @@
<b-button
v-if="fixedValueMode"
v-b-tooltip.hover.top="$t('invisibilityInfo')"
:disabled="disabledMode || !fieldCanBeInvisible(entryIndex)"
:disabled="disabledMode || !fieldCanBeInvisible"
class="innerButton"
variant="outline-secondary"
@click.prevent="changeVisibility(entryIndex)"
@click.prevent="changeVisibility"
>
<Invisible v-if="invisible" />
<Visible v-else />
</b-button>
<b-button
v-if="caseInsertField(entryIndex)"
:disabled="disabledMode || insertNewFieldButtonDisabled(nodeName)"
:disabled="disabledMode || insertNewFieldButtonDisabled"
class="innerButton"
variant="outline-secondary"
@click.prevent="insertNewField(nodeName)"
@click.prevent="insertNewFields(1)"
>
<PlusIcon />
</b-button>
<b-button
v-if="!caseInsertField(entryIndex)"
:disabled="disabledMode || removeFieldButtonDisabled(nodeName)"
:disabled="disabledMode || removeFieldButtonDisabled"
class="innerButton"
variant="outline-secondary"
@click.prevent="removeField(entryIndex, nodeName)"
@click.prevent="removeField(entryIndex)"
>
<MinusIcon />
</b-button>
......@@ -117,12 +108,9 @@ import {
import InputCombobox from './InputCombobox.vue';
import InputTextField from './InputTextField.vue';
import InputTextArea from './InputTextArea.vue';
import InputDatePicker from './InputDatePicker.vue';
import InputBooleanCombobox from './InputBooleanCombobox.vue';
import FieldReader from '../util/FieldReader';
Vue.use(VueI18n);
const i18n = new VueI18n({
......@@ -134,6 +122,15 @@ import { v4 as uuid_v4 } from 'uuid';
import type { BilingualLabels } from '@coscine/api-client/dist/types/Coscine.Api.Metadata';
import type { UserObject } from '@coscine/api-client/dist/types/Coscine.Api.User';
import type { Dataset, NamedNode, Quad_Object, Quad_Subject } from 'rdf-js';
import { prefixes } from '@zazuko/rdf-vocabularies';
import factory from 'rdf-ext';
import { getObject } from '@/util/linkedData';
import type {
FixedValueObject,
FixedValues,
ValueType,
} from '@/types/fixedValues';
export default Vue.extend({
i18n,
......@@ -151,116 +148,104 @@ export default Vue.extend({
Visible,
Invisible,
InputTextField,
InputTextArea,
InputCombobox,
InputDatePicker,
InputBooleanCombobox,
},
beforeMount() {
i18n.locale = this.languageLocale;
this.nodeName = FieldReader.getNodeName(this.formFieldInformation);
if (
this.checkField(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#datatype'
)
) {
let currentDatatype = FieldReader.getObject(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#datatype'
props: {
metadata: {
required: true,
type: Object as PropType<Dataset>,
},
fixedValues: {
default: () => ({}),
type: Object as PropType<FixedValues>,
},
fixedValueMode: {
default: false,
type: Boolean,
},
disabledMode: {
default: false,
type: Boolean,
},
languageLocale: {
default: 'en',
type: String,
},
errorMessages: {
default: () => ({}),
type: Object as PropType<{ [nodeName: string]: Quad_Object[] }>,
},
classReceiver: {
default: () => {
return async () => ({} as BilingualLabels);
},
type: Function as PropType<
(classUrl: string) => Promise<BilingualLabels>
>,
},
userReceiver: {
default: () => {
return async () => ({} as UserObject);
},
type: Function as PropType<() => Promise<UserObject>>,
},
dataset: {
required: true,
type: Object as PropType<Dataset>,
},
property: {
required: true,
type: Object as PropType<Quad_Subject>,
},
},
data() {
return {
locked: false,
invisible: false,
};
},
computed: {
classObject(): NamedNode<string> | null {
const classes = this.dataset.match(
this.property,
factory.namedNode(prefixes.sh + 'class')
);
if (
currentDatatype['value'] === 'http://www.w3.org/2001/XMLSchema#date'
) {
this.type = 'literal';
this.datatype = 'http://www.w3.org/2001/XMLSchema#date';
} else if (
currentDatatype['value'] === 'http://www.w3.org/2001/XMLSchema#boolean'
) {
this.type = 'literal';
this.datatype = 'http://www.w3.org/2001/XMLSchema#boolean';
} else {
this.type = 'literal';
}
} else {
if (
this.checkField(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#class'
)
) {
this.type = 'uri';
if (classes.size) {
for (const classEntry of classes) {
return classEntry.object as NamedNode<string>;
}
}
// if a fixed value was given directly take that
if (
this.checkDataField(
this.fixedValues[this.nodeName],
'https://purl.org/coscine/fixedValue',
0
)
) {
this.$set(
this.formData,
this.nodeName,
this.fixedValues[this.nodeName]['https://purl.org/coscine/fixedValue']
);
this.locked = true;
}
if (
this.checkDataField(
this.fixedValues[this.nodeName],
'https://purl.org/coscine/invisible',
0
)
) {
this.invisible =
this.fixedValues[this.nodeName]['https://purl.org/coscine/invisible'][0]
.value === '1';
return null;
},
componentDefinition(): string | null {
if (this.dataType) {
if (this.dataType.value === prefixes.xsd + 'date') {
return 'InputDatePicker';
} else if (this.dataType.value === prefixes.xsd + 'boolean') {
return 'InputBooleanCombobox';
}
if (!this.formDataExists(0)) {
// if formdata is still empty take the default value from the ap
if (
this.checkField(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#defaultValue'
)
) {
const defaultValues = this.extractInitializingValues(
'http://www.w3.org/ns/shacl#defaultValue'
);
this.$set(this.formData, this.nodeName, defaultValues);
return 'InputTextField';
} else {
if (this.classObject) {
return 'InputCombobox';
}
// take the default value from the provided values
if (
this.checkDataField(
this.fixedValues[this.nodeName],
'https://purl.org/coscine/defaultValue',
0
)
this.dataset.match(
this.property,
factory.namedNode(prefixes.sh + 'in')
).size
) {
this.$set(
this.formData,
this.nodeName,
this.fixedValues[this.nodeName][
'https://purl.org/coscine/defaultValue'
]
);
return 'InputTextField';
}
}
this.minCount = FieldReader.getMinCount(this.formFieldInformation);
this.maxCount = FieldReader.getMaxCount(this.formFieldInformation);
this.minLength = FieldReader.getMinLength(this.formFieldInformation);
this.valueRequired = this.minCount >= 1;
this.$emit('input');
return null;
},
computed: {
cssProps(): any {
cssProps(): {
'--wrapperInputWidth': string;
'--wrapperInputRightMargin': string;
} {
return {
// Formula: 100% - (n.Buttons)*(Button Width) - (--wrapperInputRightMargin)
'--wrapperInputWidth': this.fixedValueMode
......@@ -269,285 +254,320 @@ export default Vue.extend({
'--wrapperInputRightMargin': this.fixedValueMode ? '1rem' : '5px',
};
},
formDataIds(): Array<string> {
return this.formData[this.nodeName].map(() => {
return uuid_v4();
});
dataType(): NamedNode | undefined {
const dataTypes = this.dataset.match(
this.property,
factory.namedNode(prefixes.sh + 'datatype')
);
for (const dataType of dataTypes) {
return dataType.object as NamedNode;
}
return undefined;
},
fieldCanBeInvisible(): boolean {
return (
(this.required &&
this.values.some((entry) => entry.value !== '') &&
this.locked) ||
!this.required
);
},
fixedValueDisabled(): Array<boolean> {
return this.values.map(
(entry) =>
this.disabledMode || !entry || (this.required && this.invisible)
);
},
insertNewFieldButtonDisabled(): boolean {
if (
this.values.length === 0 ||
!this.maxCount ||
this.values.length < this.maxCount
) {
return false;
} else {
return true;
}
},
label(): string {
let label = FieldReader.getLocalizedField(
this.formFieldInformation,
const labels = getObject(
this.property,
factory.namedNode(prefixes.sh + 'name'),
this.dataset,
this.languageLocale
);
if (label === '') {
label = this.nodeName;
if (!labels.length) {
return this.nodeName;
}
return label;
return labels[0].object.value;
},
maxCount(): number | undefined {
const maxCounts = getObject(
this.property,
factory.namedNode(prefixes.sh + 'maxCount'),
this.dataset
);
if (!maxCounts.length) {
return undefined;
}
return Number(maxCounts[0].object.value);
},
minCount(): number {
const minCounts = getObject(
this.property,
factory.namedNode(prefixes.sh + 'minCount'),
this.dataset
);
if (!minCounts.length) {
return 0;
}
return Number(minCounts[0].object.value);
},
minLength(): number {
const minLengths = getObject(
this.property,
factory.namedNode(prefixes.sh + 'minLength'),
this.dataset
);
if (!minLengths.length) {
return 0;
}
return Number(minLengths[0].object.value);
},
nodeName(): string {
const names = getObject(
this.property,
factory.namedNode(prefixes.sh + 'path'),
this.dataset
);
if (!names.length) {
return '';
}
return names[0].object.value;
},
removeFieldButtonDisabled(): boolean {
if (this.values.length > this.minCount) {
return false;
} else {
return true;
}
},
required(): boolean {
return this.minCount >= 1;
},
singleLine(): boolean {
return !this.dataset.some(
(quad) =>
quad.subject === this.property &&
quad.predicate.value === 'http://datashapes.org/dash#singleLine' &&
quad.object.value === 'false'
);
},
state(): boolean | null {
if (
this.v.formData[this.nodeName] &&
this.v.formData[this.nodeName].$anyDirty
this.errorMessages[this.nodeName] &&
this.errorMessages[this.nodeName].length
) {
return !this.v.formData[this.nodeName].$anyError;
return false;
} else if (this.values.some((entry) => entry.value !== '')) {
return true;
}
return null;
},
values(): Quad_Object[] {
return Array.from(
this.metadata.match(undefined, factory.namedNode(this.nodeName))
).map((quad) => quad.object);
},
data() {
return {
type: 'literal',
datatype: '',
nodeName: '',
minCount: 1,
maxCount: 1,
minLength: 1,
locked: false,
invisible: false,
valueRequired: false,
};
},
props: {
formFieldInformation: Array,
formData: Object,
fixedValues: Object,
fixedValueMode: Boolean,
disabledMode: Boolean,
componentDefinition: String,
languageLocale: String,
errorMessages: Object,
v: Object,
classReceiver: {
type: Function as PropType<
(classUrl: string) => Promise<BilingualLabels>
>,
watch: {
fixedValues() {
this.initFixedValues();
},
metadata() {
this.initMetadata();
},
userReceiver: {
type: Function as PropType<() => Promise<UserObject>>,
},
beforeMount() {
i18n.locale = this.languageLocale;
this.initMetadata();
this.initFixedValues();
this.initDefaultValues();
},
methods: {
changeLockable(entryKey: number) {
changeLockable() {
this.locked = !this.locked;
this.setFixedValues(entryKey);
this.setFixedValues(this.values);
},
changeVisibility(entryKey: number) {
if (!this.invisible && this.fieldCanBeInvisible(entryKey)) {
changeVisibility() {
if (!this.invisible && this.fieldCanBeInvisible) {
this.invisible = true;
} else {
this.invisible = false;
}
this.setFixedValues(entryKey);
this.setFixedValues(this.values);
},
checkDataField(
data: any,
nodename: string,
entryKey: number,
property = 'value'
initDefaultValues() {
if (
!this.values.length ||
this.values.every((entry) => entry.value === '')
) {
return FieldReader.isDataValueAssignedToKey(
data,
nodename,
property,
entryKey
const fixedValueObject = this.fixedValues[this.nodeName];
const datasetMatches = this.dataset.match(
this.property,
factory.namedNode(prefixes.sh + 'defaultValue')
);
},
checkFixedField(
data: any,
nodename: string,
entryKey: number,
property = 'value'
if (
fixedValueObject &&
fixedValueObject['https://purl.org/coscine/defaultValue'] &&
fixedValueObject['https://purl.org/coscine/defaultValue'].some(
(entry) => entry.value !== ''
)
) {
return FieldReader.isFixedValueAssignedToKey(
data,
nodename,
property,
entryKey
);
},
checkField(data: any, nodename: string, property = 'value') {
return FieldReader.isValueAssignedToKey(data, nodename, property);
},
formDataExists(entryKey: number) {
return this.checkDataField(this.formData, this.nodeName, entryKey);
},
fieldCanBeInvisible(entryKey: number) {
return (
(this.valueRequired &&
this.formDataExists(entryKey) &&
this.formData[this.nodeName][entryKey]['value'] !== '' &&
this.locked) ||
!this.valueRequired
);
},
fixedValueDisabled(entryKey: number) {
return (
this.disabledMode ||
!this.formDataExists(entryKey) ||
(this.formDataExists(entryKey) &&
this.formData[this.nodeName][entryKey]['value'] === '') ||
(this.valueRequired && this.invisible)
this.input(
this.valueTypesToQuadObjects(
fixedValueObject['https://purl.org/coscine/defaultValue']
)
);
} else if (datasetMatches.size) {
this.input(Array.from(datasetMatches).map((quad) => quad.object));
}
}
},
insertNewField(nodename: string, objValue = { value: '' }) {
this.formData[nodename].push(Vue.observable(objValue));
},
insertNewFieldButtonDisabled(nodename: string): boolean {
initFixedValues() {
const fixedValueObject = this.fixedValues[this.nodeName];
if (fixedValueObject) {
const fixedValueList =
fixedValueObject['https://purl.org/coscine/fixedValue'];
if (
this.formData[nodename].length === 0 ||
this.maxCount === -1 ||
this.formData[nodename].length < this.maxCount
fixedValueList &&
fixedValueList.some((entry) => entry.value !== '')
) {
return false;
} else {
return true;
this.input(this.valueTypesToQuadObjects(fixedValueList));
this.locked = true;
}
const invisibleList =
fixedValueObject['https://purl.org/coscine/invisible'];
this.invisible =
invisibleList !== undefined &&
invisibleList.every((entry) => entry.value === '1');
}
},
caseInsertField(entryKey: number) {
return entryKey === 0 ? true : false;
},
removeEmptyEntry(entryKey: number) {
if (
this.checkFixedField(
this.fixedValues[this.nodeName],
'https://purl.org/coscine/fixedValue',
entryKey
) &&
this.fixedValues[this.nodeName]['https://purl.org/coscine/fixedValue'][
entryKey
]['value'] === ''
) {
this.$delete(
this.fixedValues[this.nodeName][
'https://purl.org/coscine/fixedValue'
],
entryKey
);
this.locked = false;
initMetadata() {
// Set values if they don't exist
if (this.values.length < this.minCount || !this.values.length) {
let numberOfNewFields = 1;
if (this.values.length < this.minCount) {
numberOfNewFields = this.minCount - this.values.length;
}
if (
this.checkFixedField(
this.fixedValues[this.nodeName],
'https://purl.org/coscine/defaultValue',
entryKey
) &&
this.fixedValues[this.nodeName][
'https://purl.org/coscine/defaultValue'
][entryKey]['value'] === ''
) {
this.$delete(
this.fixedValues[this.nodeName][
'https://purl.org/coscine/defaultValue'
],
entryKey
);
this.locked = false;
this.insertNewFields(numberOfNewFields);
}
},
removeField(entryKey: number, nodename: string) {
FieldReader.removeField(
this.formData,
this.v,
nodename,
this.$delete,
entryKey
);
input(values: Quad_Object[], fixedValueTransfer = true) {
this.$emit('input', this.nodeName, values);
if (fixedValueTransfer) {
this.updateFixedValues(values);
}
},
removeFieldButtonDisabled(nodename: string): boolean {
if (this.formData[nodename].length > this.minCount) {
return false;
inputFixedValues(object: FixedValueObject) {
this.$emit('inputFixedValues', this.nodeName, object);
},
inputValue(entryKey: number, value: Quad_Object) {
const newValues = [...this.values];
// Clone for breaking reactivity
if (value.termType === 'Literal') {
newValues[entryKey] = factory.literal(value.value, value.datatype);
} else if (value.termType === 'NamedNode') {
newValues[entryKey] = factory.namedNode(value.value);
} else {
return true;
newValues[entryKey] = value;
}
this.input(newValues);
},
insertNewFields(numberOfNewFields = 1) {
const newValues = [...this.values];
for (let i = 0; i < numberOfNewFields; i++) {
let entry: Quad_Object = factory.literal('', this.dataType);
if (!this.dataType) {
entry = factory.namedNode('');
}
newValues.push(entry);
}
this.input(newValues, false);
},
setFixedValues(entryKey: number) {
caseInsertField(entryKey: number) {
return entryKey === 0 ? true : false;
},
removeField(entryKey: number) {
const newValues = [...this.values];
newValues.splice(entryKey, 1);
this.input(newValues);
},
setFixedValues(values: Quad_Object[]) {
const currentFixedValueObject: FixedValueObject = {};
const extractedFixedValue = this.extractForFixedValues(values);
currentFixedValueObject['https://purl.org/coscine/invisible'] = [
{ value: this.invisible ? '1' : '0', type: 'literal' },
];
if (this.locked) {
this.$set(this.fixedValues, this.nodeName, {
'https://purl.org/coscine/invisible': [
{
value: this.invisible ? '1' : '0',
type: 'literal',
},
],
'https://purl.org/coscine/fixedValue': this.extractForFixedValues(),
});
currentFixedValueObject['https://purl.org/coscine/fixedValue'] =
extractedFixedValue;
} else {
this.$set(this.fixedValues, this.nodeName, {
'https://purl.org/coscine/invisible': [
{
value: this.invisible ? '1' : '0',
type: 'literal',
},
],
'https://purl.org/coscine/defaultValue': this.extractForFixedValues(),
});
currentFixedValueObject['https://purl.org/coscine/defaultValue'] =
extractedFixedValue;
}
this.removeEmptyEntry(entryKey);
this.inputFixedValues(currentFixedValueObject);
},
triggerValidation() {
this.$emit('triggerValidation');
},
updateFixedValues(entryKey: number) {
updateFixedValues(values: Quad_Object[]) {
if (this.fixedValueMode) {
if (
this.fixedValues[this.nodeName] !== undefined &&
this.fixedValues[this.nodeName][
'https://purl.org/coscine/fixedValue'
] !== undefined
this.fixedValues[this.nodeName] &&
this.fixedValues[this.nodeName]['https://purl.org/coscine/fixedValue']
) {
this.locked = true;
} else {
this.locked = false;
}
this.setFixedValues(entryKey);
this.setFixedValues(values);
}
},
extractForFixedValues() {
const exractedValues = [];
for (
let entryKey = 0;
entryKey < this.formData[this.nodeName].length;
entryKey++
) {
const valueObject = {
value: this.formData[this.nodeName][entryKey]['value'],
type: this.type,
datatype: this.datatype !== '' ? this.datatype : undefined,
extractForFixedValues(values: Quad_Object[]) {
const extractedValues: ValueType[] = [];
for (const entry of values.filter((entry) => entry.value !== '')) {
const valueObject: ValueType = {
value: entry.value,
type: entry.termType === 'Literal' ? 'literal' : 'uri',
datatype:
entry.termType === 'Literal' ? entry.datatype.value : undefined,
};
exractedValues.push(valueObject);
extractedValues.push(valueObject);
}
return exractedValues;
},
extractInitializingValues(uri: string) {
const values = [] as Array<any>;
const allObjects = FieldReader.getAllObjects(
this.formFieldInformation,
uri
);
allObjects.forEach((element: any) => {
const singleValue = {
value: element.value,
};
values.push(singleValue);
});
return values;
return extractedValues;
},
genKey(entry: any) {
genKey() {
return uuid_v4();
},
valueTypesToQuadObjects(valueTypes: ValueType[]) {
return valueTypes.map((fixedValue) =>
fixedValue.type === 'literal'
? factory.literal(
fixedValue.value,
fixedValue.datatype
? factory.namedNode(fixedValue.datatype)
: undefined
)
: factory.namedNode(fixedValue.value)
);
},
},
});
</script>
<style #scoped>
.insertButton {
justify-content: end;
text-align: end;
cursor: pointer;
}
</style>
<style>
<style scoped>
.wrapper-input {
width: var(--wrapperInputWidth);
margin-right: var(--wrapperInputRightMargin);
......
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,
})
],
},
},
......