mirror of https://github.com/rancher/dashboard.git
198 lines
7.1 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
};
|