Commits (3)
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'eslint:recommended',
'@vue/typescript/recommended',
'@vue/prettier',
'@vue/prettier/@typescript-eslint',
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
},
};
packageExtensions:
"@vue/cli-service@*":
peerDependencies:
"@vue/cli-plugin-babel": "*"
"@vue/cli-plugin-eslint": "*"
"@vue/cli-plugin-typescript": "*"
"@vue/cli-plugin-typescript@*":
peerDependencies:
"babel-loader": "*"
"fork-ts-checker-webpack-plugin@*":
dependencies:
"vue-template-compiler": "*"
peerDependencies:
"typescript": "*"
"vue-i18n@*":
dependencies:
"vue": "^2.6.12"
"vue": "^2.6.14"
"vue-material-design-icons@*":
dependencies:
"vue": "^2.6.12"
"vue-router@*":
dependencies:
"vue": "^2.6.12"
"vuex@*":
"vue": "^2.6.14"
"vuelidate@*":
dependencies:
"vue": "^2.6.12"
"vue": "^2.6.14"
"bootstrap-vue@*":
dependencies:
"vue": "^2.6.12"
"vue": "^2.6.14"
"jquery": "*"
{
"name": "@coscine/form-generator",
"version": "1.13.0",
"version": "1.14.0",
"main": "dist/index.umd.js",
"module": "dist/index.common.js",
"browser": "dist/index.umd.min.js",
......@@ -24,15 +24,12 @@
"dependencies": {
"@coscine/api-connection": "^1.30.0",
"@coscine/app-util": "^1.9.0",
"@types/jquery": "^3.5.2",
"@types/node": "^14.14.12",
"@types/rdf-js": "^4.0.0",
"@types/vuelidate": "^0.7.13",
"bootstrap-vue": "^2.20.1",
"rdf-ext": "^1.3.0",
"rdf-ext": "^1.3.4",
"rdf-parse": "^1.5.0",
"rdf-validate-shacl": "^0.2.5",
"vue": "^2.6.12",
"uuid": "^8.3.2",
"vue": "^2.6.14",
"vue-i18n": "^8.22.2",
"vue-material-design-icons": "^4.11.0",
"vue-multiselect": "^2.1.6",
......@@ -46,17 +43,27 @@
"@semantic-release/gitlab": "^6.0.5",
"@semantic-release/npm": "^7.0.6",
"@semantic-release/release-notes-generator": "^9.0.1",
"@types/jquery": "^3.5.2",
"@types/node": "^14.14.20",
"@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.12.0",
"@types/rdf-js": "^4.0.0",
"@types/uuid": "^8.3.1",
"@types/vuelidate": "^0.7.13",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-eslint": "^4.5.7",
"@vue/cli-plugin-typescript": "^4.5.7",
"@vue/cli-service": "^4.5.7",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"conventional-changelog-eslint": "3.0.9",
"core-js": "^3.8.2",
"eslint": "^7.17.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^2.2.1",
"semantic-release": "^17.3.1",
"typescript": "^4.0.3",
"vue-template-compiler": "^2.6.12"
"typescript": "^4.1.3",
"vue-template-compiler": "^2.6.14"
},
"repository": {
"type": "git",
......
......@@ -27,17 +27,10 @@ import VueI18n from 'vue-i18n';
import BootstrapVue from 'bootstrap-vue';
import Vuelidate from 'vuelidate';
import { validationMixin } from 'vuelidate';
import { required, minLength, maxLength } from 'vuelidate/lib/validators';
import { Quad } from 'rdf-js';
import { LanguageUtil } from '@coscine/app-util';
import FieldReader from './util/FieldReader';
import WrapperInput from './components/WrapperInput.vue';
import LinkedDataHandler from './base/LinkedDataHandler';
Vue.use(VueI18n);
......@@ -46,7 +39,10 @@ Vue.use(Vuelidate);
const i18n = new VueI18n({
locale: 'en',
messages: (window.coscine && coscine.i18n) ? coscine.i18n['form-generator'] : {},
messages:
window.coscine && window.coscine.i18n
? window.coscine.i18n['form-generator']
: undefined,
silentFallbackWarn: true,
});
......@@ -110,8 +106,7 @@ export default LinkedDataHandler.extend({
validations: {} as any,
};
},
validations() : any {
const me = this;
validations(): any {
if (!this.fixedValueMode) {
return {
formData: this.validations,
......@@ -137,28 +132,38 @@ export default LinkedDataHandler.extend({
},
methods: {
createValidations() {
const me = this;
const validator = {} as any;
for (const nodename of Object.keys(this.formData)) {
validator[nodename] = {
$each: {
value: {
async shaclValidated(value: any) {
me.input();
shaclValidated: async (value: any) => {
this.input();
// A debounce has been implemented based on the nodename
if (me.timeouts[nodename]) clearTimeout(me.timeouts[nodename]);
if (this.timeouts[nodename])
clearTimeout(this.timeouts[nodename]);
return new Promise((resolve, reject) => {
me.timeouts[nodename] = setTimeout(async() => {
this.timeouts[nodename] = setTimeout(async () => {
// Validate the whole data and check if for the current nodename an error is logged
const report = await me.validateMetadata(me.formData, me.quads, me.applicationProfileId);
const report = await this.validateMetadata(
this.formData,
this.quads,
this.applicationProfileId
);
let resolveValue = true;
if (report.results.some((entry: any) => entry.path.value === nodename)) {
const result = report.results.find((entry: any) => entry.path.value === nodename);
me.errorMessages[nodename] = result.message;
if (
report.results.some(
(entry: any) => entry.path.value === nodename
)
) {
const result = report.results.find(
(entry: any) => entry.path.value === nodename
);
this.errorMessages[nodename] = result.message;
resolveValue = false;
}
resolve(resolveValue);
}, me.timeoutInterval);
}, this.timeoutInterval);
});
},
},
......@@ -168,17 +173,29 @@ export default LinkedDataHandler.extend({
this.validations = validator;
},
async handleApplicationProfiles() {
this.quads = await this.retrieveQuads(this.SHACLDefinition, this.mimeType, this.applicationProfileId);
this.properties = this.retrieveProperties(this.SHACLDefinition, this.quads, this.applicationProfileId);
this.quads = await this.retrieveQuads(
this.SHACLDefinition,
this.mimeType,
this.applicationProfileId
);
this.properties = this.retrieveProperties(
this.SHACLDefinition,
this.quads,
this.applicationProfileId
);
const unmappedSubjects = this.retrievePropertyOrder(this.properties);
this.setPropertySettings(this.properties);
this.setSHACLDefinition(unmappedSubjects);
this.createValidations();
await this.validateMetadata(this.formData, this.quads, this.applicationProfileId);
await this.validateMetadata(
this.formData,
this.quads,
this.applicationProfileId
);
},
input() {
this.$emit('input', this.formData);
......@@ -195,35 +212,55 @@ export default LinkedDataHandler.extend({
setSHACLDefinition(unmappedSubjects: any) {
this.fixedValueIds = [];
this.sortedSHACLDefinition = [];
let keys = Object.keys(unmappedSubjects).sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
let keys = Object.keys(unmappedSubjects).sort(
(a, b) => parseInt(a, 10) - parseInt(b, 10)
);
for (let i = 0; i < keys.length; i++) {
this.fixedValueIds.push(unmappedSubjects[keys[i]][0].subject.value);
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.isDataValueAssignedToKey(this.formData, nodeName, 'value', 0)) {
// ToDo: replace last entry 0 by minCount defined in the application profile
this.$set(this.formData, nodeName, [{ value: '' }]);
if (!FieldReader.isDataValueAssigned(this.formData, nodeName)) {
this.$set(this.formData, nodeName, []);
for (let index = 0; index < minCount; index++) {
this.formData[nodeName].push({ value: '' });
}
} else {
for (let index = 0; index < minCount; index++) {
if (
!FieldReader.isDataValueAssignedToKey(
this.formData,
nodeName,
'value',
index
)
) {
this.formData[nodeName][index] = { value: '' };
}
}
}
}
},
checkField(data: any, nodename: string, property: string = 'value') {
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')
) {
let datatype = FieldReader.getObject(formElement, 'http://www.w3.org/ns/shacl#datatype');
if (this.checkField(formElement, 'http://www.w3.org/ns/shacl#datatype')) {
let datatype = FieldReader.getObject(
formElement,
'http://www.w3.org/ns/shacl#datatype'
);
if (datatype['value'] === 'http://www.w3.org/2001/XMLSchema#string') {
if (
this.checkField(
formElement,
'http://datashapes.org/dash#singleLine'
) &&
FieldReader.getObject(formElement, 'http://datashapes.org/dash#singleLine')[
'value'
] === 'false'
FieldReader.getObject(
formElement,
'http://datashapes.org/dash#singleLine'
)['value'] === 'false'
) {
return 'InputTextArea';
} else {
......@@ -233,16 +270,13 @@ export default LinkedDataHandler.extend({
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#date'
) {
return 'InputDatePicker';
}
else if (
} else if (
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#boolean'
) {
return 'InputBooleanCombobox';
}
} else {
if (
this.checkField(formElement, 'http://www.w3.org/ns/shacl#class')
) {
if (this.checkField(formElement, 'http://www.w3.org/ns/shacl#class')) {
return 'InputCombobox';
}
}
......@@ -258,14 +292,34 @@ export default LinkedDataHandler.extend({
}
.multiselect__input {
border: 0px !important;
height: calc(1.4em + 0.75rem + 2px);
height: auto;
padding: 0px;
}
.multiselect__tags {
border-radius: 0px;
height: calc(1.4em + 0.75rem + 2px);
min-height: calc(1.4em + 0.75rem + 2px);
padding-top: 2px;
padding-bottom: 2px;
padding-left: 5px;
}
.multiselect__tag {
background-color: #00549f !important;
}
.multiselect__select {
height: calc(1.4em + 0.75rem + 2px);
min-height: calc(1.4em + 0.75rem + 2px);
}
.multiselect,
.multiselect__input,
.multiselect__single {
font-size: 14px;
}
.multiselect__input,
.multiselect__single {
vertical-align: -webkit-baseline-middle;
margin-bottom: unset;
}
.multiselect__option--highlight {
background-color: #00549f !important;
background: #00549f !important;
......@@ -301,14 +355,7 @@ export default LinkedDataHandler.extend({
#FormGenerator .col-form-label {
font-weight: bold;
}
#FormGenerator .lockButton {
height: calc(1.4em + 0.75rem + 2px);
margin: 0px !important;
min-width: 60px !important;
width: 60px !important;
max-width: 60px !important;
}
#FormGenerator .visibilityButton {
#FormGenerator .innerButton {
height: calc(1.4em + 0.75rem + 2px);
margin: 0px !important;
min-width: 60px !important;
......
import Vue from 'vue'
import Vue from 'vue';
import factory from 'rdf-ext';
import SHACLValidator from 'rdf-validate-shacl';
......@@ -13,32 +13,22 @@ export default Vue.extend({
};
},
methods: {
async validateMetadata(formData: any, quads: Array<Quad>, applicationProfileId: string) {
async validateMetadata(
formData: any,
quads: Array<Quad>,
applicationProfileId: string
) {
// RDF/JSON => JSON-LD since the loadDataset function doesn't support RDF/JSON
const combinedDataObject = [];
const dataObject = {} as any;
dataObject['@type'] = applicationProfileId;
for (const entry of Object.keys(formData)) {
for (const metadataEntry of formData[entry]) {
if (metadataEntry['value'] !== '') {
if (metadataEntry['type'] === 'uri') {
dataObject[entry] = {
'@id': metadataEntry['value'],
'@type': metadataEntry['datatype']
};
} else {
dataObject[entry] = {
'@value': metadataEntry['value'],
'@type': metadataEntry['datatype']
};
}
}
}
}
const dataObject = this.processDataObject(formData, applicationProfileId);
combinedDataObject.push(dataObject);
// 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 = quads.filter(
(quad) =>
quad.predicate.value ===
'http://www.w3.org/2000/01/rdf-schema#subClassOf'
);
for (const subClassQuad of subClasses) {
combinedDataObject.push({
'@id': subClassQuad.subject.value,
......@@ -50,37 +40,50 @@ export default Vue.extend({
});
}
// The non-referenced shapes have also to be represented
const nonReferenced = subClasses.filter((quad) =>
!subClasses.some((subQuad) => subQuad.subject.value === quad.object.value));
const nonReferenced = subClasses.filter(
(quad) =>
!subClasses.some(
(subQuad) => subQuad.subject.value === quad.object.value
)
);
for (const nonReference of nonReferenced) {
combinedDataObject.push({
'@id': nonReference.object.value,
});
}
const data = await this.loadDataset(JSON.stringify(combinedDataObject), 'application/ld+json', null);
const data = await this.loadDataset(
JSON.stringify(combinedDataObject),
'application/ld+json',
null
);
const validator = new SHACLValidator(quads);
const report = validator.validate(data);
this.$emit('isValid', report.conforms);
return report;
},
async getQuads (data: string, mimeType: string, baseUri: string | null) : Promise<Array<Quad>> {
async getQuads(
data: string,
mimeType: string,
baseUri: string | null
): Promise<Array<Quad>> {
const input = new Readable({
read: () => {
input.push(data);
input.push(null);
}
},
});
const quads = [] as Array<Quad>;
return new Promise((resolve) => {
rdfParser.parse(input, { contentType: mimeType, baseIRI: baseUri })
rdfParser
.parse(input, { contentType: mimeType, baseIRI: baseUri })
.on('data', (quad: Quad) => {
quads.push(quad);
})
.on('end', () => resolve(quads));
});
},
async loadDataset (data: string, mimeType: string, baseUri: string | null) {
async loadDataset(data: string, mimeType: string, baseUri: string | null) {
const quads = await this.getQuads(data, mimeType, baseUri);
const dataSet = factory.dataset();
for (const quad of quads) {
......@@ -92,41 +95,67 @@ export default Vue.extend({
this.shapes = [];
this.fillShapesListRecursive(quads, this.shapes, currentShape);
},
fillShapesListRecursive(quads: Array<Quad>, shapes: Array<string>, currentShape: string) {
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)
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);
}
}
},
async retrieveQuads(SHACLDefinition: string, mimeType: string, applicationProfileId: string) : Promise<Array<Quad>> {
async retrieveQuads(
SHACLDefinition: 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> {
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));
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)));
let highestOrder = Math.max(
...properties
.filter(
(property) =>
property.predicate.value === 'http://www.w3.org/ns/shacl#order'
)
.map((property) => Number(property.object.value))
);
if (highestOrder === -Infinity) {
highestOrder = 0;
}
......@@ -135,17 +164,20 @@ export default Vue.extend({
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');
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);
unmappedSubjects[currentOrder] = properties.filter(
(property) => property.subject.value === propertySubject
);
}
return unmappedSubjects;
......@@ -153,5 +185,42 @@ export default Vue.extend({
getPropertySubjects(properties: Array<Quad>) {
return [...new Set(properties.map((property) => property.subject.value))];
},
}
processDataObject(formData: any, applicationProfileId: string) {
const dataObject = {} as any;
dataObject['@type'] = applicationProfileId;
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;
},
},
});
......@@ -29,7 +29,9 @@ Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'en',
messages:
window.coscine && coscine.i18n ? coscine.i18n['form-generator'] : {},
window.coscine && window.coscine.i18n
? window.coscine.i18n['form-generator']
: {},
silentFallbackWarn: true,
});
......@@ -70,8 +72,8 @@ export default Vue.extend({
},
data() {
return {
selectableOptions: [] as object[],
selectedOption: {} as object,
selectableOptions: [] as Record<string, unknown>[],
selectedOption: {} as Record<string, unknown>,
};
},
methods: {
......@@ -87,13 +89,12 @@ export default Vue.extend({
this.updateFixedValues(this.entryKey);
},
loadData() {
const selectedData = this.formData[this.nodeName];
// unnecessary logic for :multiselect="true"
const selectedData = [this.formData[this.nodeName][this.entryKey]];
for (const field of selectedData) {
const selectedElement = (this.selectableOptions as any).filter(
(obj: any) => {
return obj.value === (field as any).value;
}
);
const selectedElement = this.selectableOptions.filter((obj: any) => {
return obj.value === (field as any).value;
});
}
},
},
......@@ -119,9 +120,7 @@ export default Vue.extend({
});
</script>
<style>
</style>
<style></style>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
<style scoped></style>
......@@ -31,7 +31,9 @@ Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'en',
messages:
window.coscine && coscine.i18n ? coscine.i18n['form-generator'] : {},
window.coscine && window.coscine.i18n
? window.coscine.i18n['form-generator']
: {},
silentFallbackWarn: true,
});
......@@ -60,9 +62,9 @@ export default Vue.extend({
},
mounted() {
if (
typeof coscine !== 'undefined' &&
typeof (coscine as any).authorization !== 'undefined' &&
typeof (coscine as any).authorization.bearer !== 'undefined'
typeof window.coscine !== 'undefined' &&
typeof window.coscine.authorization !== 'undefined' &&
typeof window.coscine.authorization.bearer !== 'undefined'
) {
MetadataApi.getClassInstances(
this.projectId,
......@@ -94,35 +96,44 @@ export default Vue.extend({
data() {
return {
class: '',
selectableOptions: [] as object[],
selectedOptions: [] as object[],
selectableOptions: [] as Record<string, unknown>[],
selectedOptions: [] as Record<string, unknown>[],
maxNumberOfSelectedObjects: 10000,
};
},
methods: {
input() {
const array = [];
// 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 });
array.push({
value: (field as any).value,
type: 'uri',
datatype: this.class,
});
}
this.$set(this.formData[this.nodeName], this.entryKey, array);
} else {
array.push({ value: (this.selectedOptions as any).value, type: 'uri', datatype: this.class });
// 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,
});
}
this.$set(this.formData, this.nodeName, array);
if (this.v.formData[this.nodeName]) {
this.v.formData[this.nodeName].$touch();
}
this.updateFixedValues(this.entryKey);
},
loadData() {
const selectedData = this.formData[this.nodeName];
// Logic for :multiselect="true".
const selectedData = [this.formData[this.nodeName][this.entryKey]];
for (const field of selectedData) {
const selectedElement = (this.selectableOptions as any).filter(
(obj: any) => {
return obj.value === (field as any).value;
}
);
const selectedElement = this.selectableOptions.filter((obj: any) => {
return obj.value === (field as any).value;
});
if (selectedElement[0] !== undefined) {
this.selectedOptions.push(selectedElement[0]);
}
......@@ -151,9 +162,7 @@ export default Vue.extend({
});
</script>
<style>
</style>
<style></style>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
<style scoped></style>
......@@ -10,7 +10,7 @@
:required="required"
calendar-width="100%"
start-weekday="1"
:state="v.formData[nodeName] && v.formData[nodeName].$each[entryKey]['value'].$dirty ? !v.formData[nodeName].$each[entryKey]['value'].$error : null"
:state="state"
/>
</template>
......@@ -58,12 +58,17 @@ export default Vue.extend({
updateFixedValues: Function,
v: Object,
entryKey: Number,
state: {
type: [Boolean, Object],
default: null,
},
},
methods: {
replacePlaceholder() {
if (this.checkDataField(this.formData, this.nodeName, this.entryKey)) {
if (this.formData[this.nodeName][this.entryKey]['value'] === '{TODAY}') {
if (
this.formData[this.nodeName][this.entryKey]['value'] === '{TODAY}'
) {
if (this.fixedValueMode) {
return;
}
......@@ -71,17 +76,20 @@ export default Vue.extend({
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;
} else if (this.formData[this.nodeName][this.entryKey]['value'] === '') {
this.selectedDate = yyyy + '-' + mm + '-' + dd;
} else if (
this.formData[this.nodeName][this.entryKey]['value'] === ''
) {
this.selectedDate = '';
} else {
this.selectedDate = this.formData[this.nodeName][this.entryKey]['value'];
this.selectedDate =
this.formData[this.nodeName][this.entryKey]['value'];
}
this.$set(
this.formData[this.nodeName][this.entryKey],
'value',
this.selectedDate,
this.selectedDate
);
}
},
......@@ -101,23 +109,23 @@ export default Vue.extend({
</script>
<style>
.formgeneratordatepicker label {
display: flex;
align-items: center;
}
.formgeneratordatepicker div.is-valid {
background-image: unset;
}
.formgeneratordatepicker footer {
display: none;
}
.formgeneratordatepicker button {
min-width: unset;
}
.b-calendar button {
min-width: unset;
}
.formgeneratordatepicker label {
display: flex;
align-items: center;
}
.formgeneratordatepicker div.is-valid {
background-image: unset;
}
.formgeneratordatepicker footer {
display: none;
}
.formgeneratordatepicker button {
min-width: unset;
margin: unset;
}
.b-calendar button {
min-width: unset;
}
</style>
<style scoped>
</style>
<style scoped></style>
......@@ -5,7 +5,7 @@
@input="input"
:disabled="disabledMode || locked"
:required="required"
:state="v.formData[nodeName] && v.formData[nodeName].$each[entryKey]['value'].$dirty ? !v.formData[nodeName].$each[entryKey]['value'].$error : null"
:state="state"
></b-form-textarea>
</template>
......@@ -56,6 +56,10 @@ export default Vue.extend({
updateFixedValues: Function,
v: Object,
entryKey: Number,
state: {
type: [Boolean, Object],
default: null,
},
},
methods: {
input() {
......@@ -75,15 +79,20 @@ export default Vue.extend({
replacePlaceholder() {
if (!this.fixedValueMode) {
if (this.checkDataField(this.formData, this.nodeName, this.entryKey)) {
if (this.formData[this.nodeName][this.entryKey]['value'] === "{ME}") {
if ((typeof coscine !== "undefined") && (typeof (coscine as any).authorization !== "undefined") && (typeof (coscine as any).authorization.bearer !== "undefined")) {
if (this.formData[this.nodeName][this.entryKey]['value'] === '{ME}') {
if (
typeof window.coscine !== 'undefined' &&
typeof window.coscine.authorization !== 'undefined' &&
typeof window.coscine.authorization.bearer !== 'undefined'
) {
UserApi.getUser((response: any) => {
this.$set(
this.formData[this.nodeName][this.entryKey],
'value',
response.data.displayName
);
this.textValue = this.formData[this.nodeName][this.entryKey]['value'];
this.textValue =
this.formData[this.nodeName][this.entryKey]['value'];
this.$forceUpdate();
});
}
......
......@@ -5,7 +5,7 @@
@input="input"
:disabled="disabledMode || locked"
:required="required"
:state="v.formData[nodeName] && v.formData[nodeName].$each[entryKey]['value'].$dirty ? !v.formData[nodeName].$each[entryKey]['value'].$error : null"
:state="state"
/>
</template>
......@@ -56,6 +56,10 @@ export default Vue.extend({
updateFixedValues: Function,
v: Object,
entryKey: Number,
state: {
type: [Boolean, Object],
default: null,
},
},
methods: {
input() {
......@@ -76,14 +80,19 @@ export default Vue.extend({
if (!this.fixedValueMode) {
if (this.checkDataField(this.formData, this.nodeName, this.entryKey)) {
if (this.formData[this.nodeName][this.entryKey]['value'] === '{ME}') {
if ((typeof coscine !== "undefined") && (typeof (coscine as any).authorization !== "undefined") && (typeof (coscine as any).authorization.bearer !== "undefined")) {
if (
typeof window.coscine !== 'undefined' &&
typeof window.coscine.authorization !== 'undefined' &&
typeof window.coscine.authorization.bearer !== 'undefined'
) {
UserApi.getUser((response: any) => {
this.$set(
this.formData[this.nodeName][this.entryKey],
'value',
response.data.displayName
);
this.textValue = this.formData[this.nodeName][this.entryKey]['value'];
this.textValue =
this.formData[this.nodeName][this.entryKey]['value'];
this.$forceUpdate();
});
}
......
<template>
<div :class="componentDefinition" v-if="componentDefinition !== null" :style="cssProps">
<div
:class="componentDefinition"
v-if="componentDefinition !== null"
:style="cssProps"
>
<b-form-group
v-bind:class="{
:class="{
mandatory: valueRequired,
invisible: !fixedValueMode && invisible,
}"
:label-for="cssid"
label-cols-sm="3"
label-align-sm="right"
:label="label"
label-class="application-profile-label"
>
<!-- ToDo: replace [0] with the range if minCount/maxCount etc. -->
<b-button-toolbar v-for="(entry, entryIndex) in [0]" v-bind:key="entry">
<b-button-toolbar
class="my-1"
v-for="(entry, entryIndex) in formData[nodeName]"
:key="formDataIds[entryIndex]"
>
<component
class="wrapper-input"
:is="componentDefinition"
:cssId="cssId"
:cssId="formDataIds[entryIndex]"
:invisible="invisible"
:locked="locked"
:required="valueRequired"
......@@ -34,11 +40,13 @@
:updateFixedValues="updateFixedValues"
:v="v"
:entryKey="entryIndex"
:state="state"
/>
<b-button-group v-if="fixedValueMode" class="wrapper-input-button">
<b-button-group class="wrapper-input-button">
<b-button
v-if="fixedValueMode"
:disabled="fixedValueDisabled(entryIndex)"
class="lockButton"
class="innerButton"
variant="outline-secondary"
@click.prevent="changeLockable(entryIndex)"
>
......@@ -46,18 +54,37 @@
<LockOpenIcon v-else />
</b-button>
<b-button
v-b-tooltip.hover.bottom="$t('invisibilityInfo')"
v-if="fixedValueMode"
v-b-tooltip.hover.top="$t('invisibilityInfo')"
:disabled="disabledMode || !fieldCanBeInvisible(entryIndex)"
class="visibilityButton"
class="innerButton"
variant="outline-secondary"
@click.prevent="changeVisibility(entryIndex)"
>
<Invisible v-if="invisible" />
<Visible v-else />
</b-button>
<b-button
v-if="caseInsertField(entryIndex)"
:disabled="disabledMode || insertNewFieldButtonDisabled(nodeName)"
class="innerButton"
variant="outline-secondary"
@click.prevent="insertNewField(nodeName)"
>
<PlusIcon />
</b-button>
<b-button
v-if="!caseInsertField(entryIndex)"
:disabled="disabledMode || removeFieldButtonDisabled(nodeName)"
class="innerButton"
variant="outline-secondary"
@click.prevent="removeField(entryIndex, nodeName)"
>
<MinusIcon />
</b-button>
</b-button-group>
</b-button-toolbar>
<b-form-invalid-feedback :state="v.formData[nodeName] && v.formData[nodeName].$dirty ? !v.formData[nodeName].$error : null">
<b-form-invalid-feedback :state="state">
<div v-for="(message, index) in errorMessages[nodeName]" :key="index">
{{ message.value }}
</div>
......@@ -72,6 +99,8 @@ import VueI18n from 'vue-i18n';
import LockIcon from 'vue-material-design-icons/Lock.vue';
import LockOpenIcon from 'vue-material-design-icons/LockOpen.vue';
import MinusIcon from 'vue-material-design-icons/Minus.vue';
import PlusIcon from 'vue-material-design-icons/Plus.vue';
import Visible from 'vue-material-design-icons/Eye.vue';
import Invisible from 'vue-material-design-icons/EyeOff.vue';
......@@ -87,9 +116,13 @@ Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'en',
messages: (window.coscine && coscine.i18n) ? coscine.i18n['form-generator'] : {},
messages:
window.coscine && window.coscine.i18n
? window.coscine.i18n['form-generator']
: {},
silentFallbackWarn: true,
});
import { v4 as uuid_v4 } from 'uuid';
export default Vue.extend({
i18n,
......@@ -97,13 +130,15 @@ export default Vue.extend({
components: {
LockIcon,
LockOpenIcon,
MinusIcon,
PlusIcon,
Visible,
Invisible,
InputTextField,
InputTextArea,
InputCombobox,
InputDatePicker,
InputBooleanCombobox
InputBooleanCombobox,
},
beforeMount() {
i18n.locale = this.languageLocale;
......@@ -112,19 +147,25 @@ export default Vue.extend({
this.languageLocale
);
this.nodeName = FieldReader.getNodeName(this.formFieldInformation);
this.cssId = this.nodeName.substr(this.nodeName.lastIndexOf('\/') + 1);
if (this.cssId.indexOf('#') !== -1) {
this.cssId = this.cssId.substr(this.cssId.indexOf('#') + 1);
}
if (
this.checkField(this.formFieldInformation, 'http://www.w3.org/ns/shacl#datatype')
this.checkField(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#datatype'
)
) {
let currentDatatype = FieldReader.getObject(this.formFieldInformation, 'http://www.w3.org/ns/shacl#datatype');
if (currentDatatype['value'] === 'http://www.w3.org/2001/XMLSchema#date') {
let currentDatatype = FieldReader.getObject(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#datatype'
);
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') {
} else if (
currentDatatype['value'] === 'http://www.w3.org/2001/XMLSchema#boolean'
) {
this.type = 'literal';
this.datatype = 'http://www.w3.org/2001/XMLSchema#boolean';
} else {
......@@ -132,7 +173,10 @@ export default Vue.extend({
}
} else {
if (
this.checkField(this.formFieldInformation, 'http://www.w3.org/ns/shacl#class')
this.checkField(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#class'
)
) {
this.type = 'uri';
}
......@@ -145,16 +189,13 @@ export default Vue.extend({
'https://purl.org/coscine/fixedValue'
)
) {
this.$set(this.formData, this.nodeName, [
{
value: FieldReader.getObject(this.formFieldInformation,
'https://purl.org/coscine/fixedValue'
).value,
},
]);
const fixedValues = this.extractInitializingValues(
'https://purl.org/coscine/fixedValue'
);
this.$set(this.formData, this.nodeName, fixedValues);
this.locked = true;
}
// if a fixed values was given directly take that
// if a fixed value was given directly take that
if (
this.checkDataField(
this.fixedValues[this.idForFixedValues],
......@@ -162,9 +203,13 @@ export default Vue.extend({
0
)
) {
this.$set(this.formData, this.nodeName, this.fixedValues[
this.idForFixedValues
]['https://purl.org/coscine/fixedValue']);
this.$set(
this.formData,
this.nodeName,
this.fixedValues[this.idForFixedValues][
'https://purl.org/coscine/fixedValue'
]
);
this.locked = true;
}
......@@ -175,8 +220,10 @@ export default Vue.extend({
)
) {
this.invisible =
FieldReader.getObject(this.formFieldInformation, 'https://purl.org/coscine/invisible')
.value === '1';
FieldReader.getObject(
this.formFieldInformation,
'https://purl.org/coscine/invisible'
).value === '1';
}
if (
this.checkDataField(
......@@ -192,20 +239,17 @@ export default Vue.extend({
}
if (!this.formDataExists(0)) {
// if formdata is still emty take the default value from the ap
// if formdata is still empty take the default value from the ap
if (
this.checkField(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#defaultValue'
)
) {
this.$set(this.formData, this.nodeName, [
{
value: FieldReader.getObject(this.formFieldInformation,
'http://www.w3.org/ns/shacl#defaultValue'
).value,
},
]);
const defaultValues = this.extractInitializingValues(
'http://www.w3.org/ns/shacl#defaultValue'
);
this.$set(this.formData, this.nodeName, defaultValues);
}
// take the default value from the alt provided values of the ap
if (
......@@ -214,13 +258,10 @@ export default Vue.extend({
'https://purl.org/coscine/defaultValue'
)
) {
this.$set(this.formData, this.nodeName, [
{
value: FieldReader.getObject(this.formFieldInformation,
'https://purl.org/coscine/defaultValue'
).value
},
]);
const defaultValues = this.extractInitializingValues(
'https://purl.org/coscine/defaultValue'
);
this.$set(this.formData, this.nodeName, defaultValues);
}
// take the default value from the provided values
if (
......@@ -230,26 +271,45 @@ export default Vue.extend({
0
)
) {
this.$set(this.formData, this.nodeName, this.fixedValues[this.idForFixedValues]['https://purl.org/coscine/defaultValue']);
this.$set(
this.formData,
this.nodeName,
this.fixedValues[this.idForFixedValues][
'https://purl.org/coscine/defaultValue'
]
);
}
}
this.valueRequired =
this.checkField(
this.formFieldInformation,
'http://www.w3.org/ns/shacl#minCount'
) &&
FieldReader.getObject(this.formFieldInformation, 'http://www.w3.org/ns/shacl#minCount')[
'value'
] >= 1;
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');
},
computed: {
cssProps(): any {
return {
'--wrapperInputWidth': (this.fixedValueMode) ? 'calc(100% - 121px - 1rem)' : '100%',
'--wrapperInputRightMargin': (this.fixedValueMode) ? '1rem' : '0rem',
// Formula: 100% - (n.Buttons)*(Button Width) - (--wrapperInputRightMargin)
'--wrapperInputWidth': this.fixedValueMode
? 'calc(100% - 181px - 1rem)'
: 'calc(100% - 61px - 5px)',
'--wrapperInputRightMargin': this.fixedValueMode ? '1rem' : '5px',
};
},
formDataIds(): Array<string> {
return this.formData[this.nodeName].map(() => {
return uuid_v4();
});
},
state(): boolean | null {
if (
this.v.formData[this.nodeName] &&
this.v.formData[this.nodeName].$anyDirty
) {
return !this.v.formData[this.nodeName].$anyError;
}
return null;
},
},
data() {
return {
......@@ -257,7 +317,9 @@ export default Vue.extend({
datatype: '',
label: 'Label:',
nodeName: '',
cssId: 'cssId',
minCount: 1,
maxCount: 1,
minLength: 1,
locked: false,
invisible: false,
valueRequired: false,
......@@ -290,13 +352,33 @@ export default Vue.extend({
}
this.setFixedValues(entryKey);
},
checkDataField(data: any, nodename: string, entryKey: number, property: string = 'value') {
return FieldReader.isDataValueAssignedToKey(data, nodename, property, entryKey);
checkDataField(
data: any,
nodename: string,
entryKey: number,
property = 'value'
) {
return FieldReader.isDataValueAssignedToKey(
data,
nodename,
property,
entryKey
);
},
checkFixedField(data: any, nodename: string, entryKey: number, property: string = 'value') {
return FieldReader.isFixedValueAssignedToKey(data, nodename, property, entryKey);
checkFixedField(
data: any,
nodename: string,
entryKey: number,
property = 'value'
) {
return FieldReader.isFixedValueAssignedToKey(
data,
nodename,
property,
entryKey
);
},
checkField(data: any, nodename: string, property: string = 'value') {
checkField(data: any, nodename: string, property = 'value') {
return FieldReader.isValueAssignedToKey(data, nodename, property);
},
formDataExists(entryKey: number) {
......@@ -312,62 +394,113 @@ export default Vue.extend({
);
},
fixedValueDisabled(entryKey: number) {
return this.disabledMode ||
!this.formDataExists(entryKey) ||
(this.formDataExists(entryKey) && this.formData[this.nodeName][entryKey]['value'] === '') ||
(this.valueRequired && this.invisible)
return (
this.disabledMode ||
!this.formDataExists(entryKey) ||
(this.formDataExists(entryKey) &&
this.formData[this.nodeName][entryKey]['value'] === '') ||
(this.valueRequired && this.invisible)
);
},
insertNewField(nodename: string, objValue = { value: '' }) {
this.formData[nodename].push(Vue.observable(objValue));
},
insertNewFieldButtonDisabled(nodename: string): boolean {
if (
this.formData[nodename].length === 0 ||
this.maxCount === -1 ||
this.formData[nodename].length < this.maxCount
) {
return false;
} else {
return true;
}
},
caseInsertField(entryKey: number) {
return entryKey === 0 ? true : false;
},
removeEmptyEntry(entryKey: number){
if ((this.checkFixedField(this.fixedValues[this.idForFixedValues],'https://purl.org/coscine/fixedValue', entryKey) && this.fixedValues[this.idForFixedValues]['https://purl.org/coscine/fixedValue'][entryKey]['value'] === '') ||
this.checkFixedField(this.fixedValues[this.idForFixedValues],'https://purl.org/coscine/defaultValue', entryKey) && this.fixedValues[this.idForFixedValues]['https://purl.org/coscine/defaultValue'][entryKey]['value'] === '') {
this.$delete(this.fixedValues[this.idForFixedValues],'https://purl.org/coscine/defaultValue');
this.$delete(this.fixedValues[this.idForFixedValues],'https://purl.org/coscine/fixedValue');
removeEmptyEntry(entryKey: number) {
if (
this.checkFixedField(
this.fixedValues[this.idForFixedValues],
'https://purl.org/coscine/fixedValue',
entryKey
) &&
this.fixedValues[this.idForFixedValues][
'https://purl.org/coscine/fixedValue'
][entryKey]['value'] === ''
) {
this.$delete(
this.fixedValues[this.idForFixedValues][
'https://purl.org/coscine/fixedValue'
],
entryKey
);
this.locked = false;
}
if (
this.checkFixedField(
this.fixedValues[this.idForFixedValues],
'https://purl.org/coscine/defaultValue',
entryKey
) &&
this.fixedValues[this.idForFixedValues][
'https://purl.org/coscine/defaultValue'
][entryKey]['value'] === ''
) {
this.$delete(
this.fixedValues[this.idForFixedValues][
'https://purl.org/coscine/defaultValue'
],
entryKey
);
this.locked = false;
}
},
removeField(entryKey: number, nodename: string) {
FieldReader.removeField(
this.formData,
this.v,
nodename,
this.$delete,