VaForm uses the validation library vuelidate. The following example shows a form validation example with the Vuelidate library:
olobase-demo-ui/src/resources/Companies/Form.vue
<template>
<va-form :id="id" :item="item" disable-redirect v-model="model">
<v-row>
<v-col>
<va-text-input source="companyName" :error-messages="companyNameErrors"></va-text-input>
<va-text-input source="companyShortName" :error-messages="companyShortNameErrors"></va-text-input>
<va-text-input source="taxOffice" :error-messages="taxOfficeErrors"></va-text-input>
<va-text-input source="taxNumber" :error-messages="taxNumberErrors"></va-text-input>
<va-text-input source="address" :error-messages="addressErrors"></va-text-input>
<va-save-button></va-save-button>
</v-col>
</v-row>
</va-form>
</template>
<script>
import Utils from "vuetify-admin/src/mixins/utils";
import { useVuelidate } from "@vuelidate/core";
import { required, maxLength, numeric } from "@vuelidate/validators";
import { provide } from 'vue';
export default {
props: ["id", "item"],
mixins: [Utils],
setup() {
let vuelidate = useVuelidate();
provide('v$', vuelidate)
return { v$: vuelidate }
},
data() {
return {
model: {
id: null,
companyName: null,
companyShortName: null,
taxOffice: null,
taxNumber: null,
address: null,
},
};
},
validations: {
model: {
companyName: {
required,
maxLength: maxLength(160),
},
companyShortName: {
required,
maxLength: maxLength(60),
},
taxOffice: {
maxLength: maxLength(100),
},
taxNumber: {
numeric,
maxLength: maxLength(60),
},
address: {
maxLength: maxLength(255),
},
},
},
computed: {
companyNameErrors() {
const errors = [];
const field = "companyName";
if (!this.v$["model"][field].$dirty) return errors;
this.v$["model"][field].required.$invalid &&
errors.push(this.$t("v.text.required"));
this.v$["model"][field].maxLength.$invalid &&
errors.push(this.$t("v.string.maxLength", { max: "160" }));
return errors;
},
companyShortNameErrors() {
const errors = [];
const field = "companyShortName";
if (!this.v$["model"][field].$dirty) return errors;
this.v$["model"][field].required.$invalid &&
errors.push(this.$t("v.text.required"));
this.v$["model"][field].maxLength.$invalid &&
errors.push(this.$t("v.string.maxLength", { max: "60" }));
return errors;
},
taxOfficeErrors() {
const errors = [];
const field = "taxOffice";
if (!this.v$["model"][field].$dirty) return errors;
this.v$["model"][field].maxLength.$invalid &&
errors.push(this.$t("v.string.maxLength", { max: "100" }));
return errors;
},
taxNumberErrors() {
const errors = [];
const field = "taxNumber";
if (!this.v$["model"][field].$dirty) return errors;
this.v$["model"][field].numeric.$invalid &&
errors.push(this.$t("v.number.numeric"));
this.v$["model"][field].maxLength.$invalid &&
errors.push(this.$t("v.string.maxLength", { max: "60" }));
return errors;
},
addressErrors() {
const errors = [];
const field = "address";
if (!this.v$["model"][field].$dirty) return errors;
this.v$["model"][field].maxLength.$invalid &&
errors.push(this.$t("v.string.maxLength", { max: "255" }));
return errors;
},
},
created() {
this.model.id = this.generateUid();
}
};
</script>
Vue.js provide method is used to perform form validation in list-view data update tables. validations rules and errors messages regarding validation must be declared in the provide method.
olobase-demo-ui/src/resources/Permissions/List.vue
<template>
<va-list disable-create :fields="fields" :filters="filters" :items-per-page="50">
<va-data-table-server :group-by="groupBy" row-create row-clone row-edit disable-edit disable-show disable-clone disable-create-redirect>
</va-data-table-server>
</va-list>
</template>
<script>
import { required } from "@vuelidate/validators";
export default {
props: ["resource"],
provide() {
return {
validations: {
form: {
moduleName: {
required
},
resource: {
required
},
route: {
required
},
action: {
required
},
method: {
required
}
}
},
errors: {
moduleNameErrors: (v$) => {
const errors = [];
if (!v$['form'].moduleName.$dirty) return errors;
v$['form'].moduleName.required.$invalid &&
errors.push(this.$t("v.text.required"));
return errors;
},
resourceErrors: (v$) => {
const errors = [];
if (!v$['form'].resource.$dirty) return errors;
v$['form'].resource.required.$invalid &&
errors.push(this.$t("v.text.required"));
return errors;
},
actionErrors: (v$) => {
const errors = [];
if (!v$['form'].action.$dirty) return errors;
v$['form'].action.required.$invalid &&
errors.push(this.$t("v.text.required"));
return errors;
},
routeErrors: (v$) => {
const errors = [];
if (!v$['form'].route.$dirty) return errors;
v$['form'].route.required.$invalid &&
errors.push(this.$t("v.text.required"));
return errors;
},
methodErrors: (v$) => {
const errors = [];
if (!v$['form'].method.$dirty) return errors;
v$['form'].method.required.$invalid &&
errors.push(this.$t("v.text.required"));
return errors;
},
}
};
},
data() {
return {
groupBy: [{ key: 'moduleName' }],
selected: [],
filters: [],
fields: [
{
source: "data-table-group",
label: this.$t("va.datatable.group"),
sortable: false,
},
{
source: "moduleName",
sortable: true,
},
{
source: "resource",
sortable: true,
},
{
source: "action",
type: "select",
sortable: true,
},
{
source: "route",
sortable: true,
},
{
source: "method",
type: "select",
sortable: true,
},
],
};
}
};
</script>
After all verification fields are completed on the client side, verification is performed on the server side. If your API finds a validation error, it sends the response to the client as follows, containing errors for all validation fields, with the response body always using a 400 status code.
{
"data": {
"error": {
"firstname": [
"firstname: Value is required and can't be empty"
],
"lastname": [
"lastname: Value is required and can't be empty"
]
}
}
}
Afterwards, the function called parseApiErrors in the useHttp.js plugin analyzes the errors returned from the server and prints the errors one by one on the screen with a status message as follows.
If a single error is sent, the server response will be as follows. On the client side, this error will be displayed as above.
{
"data": {
"error": "Example single line error"
}
}
src/plugins/useHttp.js
/**
* parse validation errors
*/
function parseApiErrors(error) {
if (error.response["data"]
&& error.response["data"]["data"]
&& error.response["data"]["data"]["error"]) {
let errorHtml = ""
let hasError = false
let errorObject = error.response.data.data.error
if (errorObject instanceof Object) {
errorHtml = "<ul>";
Object.keys(errorObject).forEach(function (k) {
if (Array.isArray(errorObject[k])) {
hasError = true;
errorObject[k].forEach(function (subObject) {
if (typeof subObject === "string") {
errorHtml += '<li>' + `${subObject}` + '</li>'
} else if (typeof subObject === "object") {
Object.values(subObject).forEach(function (subErrors) {
if (Array.isArray(subErrors)) {
subErrors.forEach(function (strError) {
errorHtml += '<li>' + `${strError}` + '</li>'
});
}
});
}
});
} else {
hasError = true;
errorHtml += '<li>' + `${errorObject[k]}` + '</li>'
}
})
errorHtml += "</ul>";
} else if (typeof errorObject === "string") {
errorHtml = errorObject
if (errorObject == "Token Expired") {
store.dispatch("auth/logout");
} else {
hasError = true
}
}
if (hasError) {
store.commit("messages/show", { type: 'error', message: errorHtml })
}
return error;
}
}
In accordance with the principle of separate operation of the frontend and backend, the translations of the labels of the input fields should be defined one by one in the php file called label.php in your backend application in order not to re-define them on the frontend side. The following example shows a translation example of the input names firstname and lastname.
{
"data": {
"error": {
"firstname": [
"Firstname: Value is required and can't be empty"
],
"lastname": [
"Lastname: Value is required and can't be empty"
]
}
}
}
data/language/en/labels.php
<?php
return [
// login
'username' => 'E-Mail',
'password' => 'Password',
'email' => 'E-Mail',
// Account
//
'firstname' => 'Firstname',
'lastname' => 'Lastname',
];