dashboard/shell/mixins/form-validation.js

198 lines
7.1 KiB
JavaScript

import { getAllValues } from '@shell/utils/object';
import formRulesGenerator from '@shell/utils/validators/formRules/index';
export default {
data() {
return {
/**
* Define the validation rules for the entire form.
* These should almost always be overridden in the form-component using the mixin
*
* path (required): defines the path of the value to be tested against it's rules. Looks for the relevant path in `this.value` unless an is passed in via rootObject
* rules (required): array of strings that match which validator functions to run against the value of the field defined by the path (and optionally the rulesets rootObject),
* rootObject (optional): redirects the path to the object passed here,
* translationKey (optional): defines the displaykey, overrides displaykeys that may otherwise be passed into the translation
*
* NOTE: path of type 'value.value' will be only 'value'
*
* Example:
* {
* path: 'container.image',
* rules: ['noSpaces', 'noPeriods'],
* rootObject: { container: { image: 'name' } },
* translationKey: 'Image Name',
* }
*/
fvFormRuleSets: [],
/**
* An array of strings that track which ruleset paths have been bound to a field for error
* reporting tracked in a separate array from the actual rulesets since we want the option
* of keeping track of modelValidationRules without mutating the model itself you may place
* a path in here manually as part of your form's data props or you may place a path in
* here programmatically by using the "fvGetAndReportPathRules" method
*/
fvReportedValidationPaths: []
};
},
methods: {
/**
* Returns an array of validator functions based off path property of the ruleset, use this if you want
* the array but you don't want the form to track that the rules have been bound to a field
* @param {*} path
* @returns
*/
fvGetPathRules(path) {
return this.fvRulesets.find((ruleset) => ruleset.path === path)?.rules || [];
},
/**
* Returns an array of validator functions and pushes the path of the relevant ruleset into
* fvReportedValidationPaths so that we know any error messages are handled by the field using it
* @param {*} path
* @returns
*/
fvGetAndReportPathRules(path) {
const rules = this.fvGetPathRules(path);
if (rules.length > 0 && !this.fvReportedValidationPaths.includes(path)) {
this.fvReportedValidationPaths = [...this.fvReportedValidationPaths, path];
}
return rules;
},
/**
* Validates that the path is one that belongs to a ruleset (either a formRuleset or from the
* modelValidationRules) and returns its value(s) in an array
* @param {*} path
* @returns
*/
fvGetPathValues(path) { //
// returns even single values as an array to simplify validation logic since
// some fields may have multiple values
const relevantRuleset = this.fvRulesets.find((ruleset) => ruleset.path === path);
if (!relevantRuleset) {
return [];
}
return getAllValues(relevantRuleset?.rootObject || this.value, relevantRuleset?.path);
},
fvGetValues(val, idx, arr) {
return (arr.length > 1 &&
typeof val === 'object' &&
!Array.isArray(val) &&
val !== null ? { ...val, idx } : val
);
},
/**
* Gets errors from multiple paths, usually used externally to check a single path but used
* within the mixin to check all paths for errors
* @param {*} paths
* @returns
*/
fvGetPathErrors(paths = []) {
const messages = paths.reduce((acc, path) => {
const pathErrors = [];
const relevantRules = this.fvGetPathRules(path);
const relevantValues = this.fvGetPathValues(path).map(this.fvGetValues);
relevantRules.forEach((rule) => {
relevantValues.forEach((value) => {
pathErrors.push(rule(value));
});
});
return [...acc, ...pathErrors].filter(Boolean);
}, []);
return messages;
},
},
computed: {
/**
* fvExtraRules allows you to create rules that might be specific to a form inside of
* that form component and pass them into the mixin's logic.
* fvExtraRules needs to return an object with a validation rule function in each key.
* This is a computed property as returning functions in the data props is not considered
* a best practice
* @returns
*/
fvExtraRules() {
return {};
},
/**
* Rulesets is a combination of the rules defined in the fvFormRuleSets array and the
* modelValidationRules in the model. Theoretically, a form could just use the rulesets
* defined in the model however in practice this can be limiting
* @returns
*/
fvRulesets() {
const nullValidator = () => undefined;
return [
...this.fvFormRuleSets.map((ruleset) => {
const formRules = {
...formRulesGenerator(
this.$store.getters['i18n/t'],
{ displayKey: ruleset?.translationKey ? this.$store.getters['i18n/t'](ruleset.translationKey) : 'Value' }),
...this.fvExtraRules
};
return {
...ruleset,
rules: ruleset.rules.map((rule) => formRules[rule] || nullValidator),
formValidationRule: true
};
}),
...(this?.value?.modelValidationRules || []).map((rule) => ({
...rule,
formValidationRule: false
}))
];
},
/**
* If either the fvFormRuleSets or the modelValidationRules throw an error and the associated path
* isn't in the reportValidationPaths then it'll show up here.
* Useful for throwing unreported errors into a generic banner
* @returns
*/
fvUnreportedValidationErrors() { //
const paths = this.fvRulesets
.filter((ruleset) => !!ruleset.formValidationRule && !this.fvReportedValidationPaths.includes(ruleset.path))
.map((ruleset) => ruleset.path);
const formErrors = this.fvGetPathErrors(paths);
// the model already has a means of producing errors, not reinventing the wheel... yet...
const modelErrors = this.value.customValidationErrors ? this.value.customValidationErrors(this.value, this.fvReportedValidationPaths) : [];
return [...formErrors, ...modelErrors, ...(this.errors || [])];
},
/**
* Checks for any and all errors, regardless of being bound, from the model, or from the form
* @returns
*/
fvValidationErrors() {
const paths = this.fvRulesets.filter((ruleset) => !!ruleset.formValidationRule).map((ruleset) => ruleset.path);
const formErrors = this.fvGetPathErrors(paths);
// the model already has a means of producing errors, not reinventing the wheel... yet...
const modelErrors = this.value.customValidationErrors ? this.value.customValidationErrors(this.value) : [];
return [...formErrors, ...modelErrors];
},
fvFormIsValid() {
return this.fvValidationErrors.length === 0;
}
}
};