Skip to content
37 changes: 35 additions & 2 deletions src/ValidationsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ class Validations {
async addValidations(validations) {
throw 'Abstract method addValidations not implemented', validations;
}

/**
* Check if element/container is visible.
*/
isVisible() {
// Disable validations if field is hidden
let visible = true;
if (this.element.config.conditionalHide) {
try {
visible = !!Parser.evaluate(this.element.config.conditionalHide, this.data);
} catch (error) {
visible = false;
}
}
return visible;
}
}

/**
Expand Down Expand Up @@ -60,6 +76,10 @@ class ScreenValidations extends Validations {
*/
class FormNestedScreenValidations extends Validations {
async addValidations(validations) {
// Disable validations if field is hidden
if (!this.isVisible()) {
return;
}
const definition = await this.loadScreen(this.element.config.screen);
if (definition && definition[0] && definition[0].items) {
await ValidationsFactory(definition[0].items, { screen: this.screen, data: this.data }).addValidations(validations);
Expand All @@ -85,10 +105,14 @@ class FormNestedScreenValidations extends Validations {
*/
class FormLoopValidations extends Validations {
async addValidations(validations) {
// Disable validations if field is hidden
if (!this.isVisible()) {
return;
}
set(validations, this.element.config.name, {});
const loopField = get(validations, this.element.config.name);
loopField['$each'] = [];
const firstRow = this.data[0] || {};
loopField['$each'] = {};
const firstRow = (get(this.data, this.element.config.name) || [{}])[0];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the get from lodash has a default value if the conditional is not valid or the value is not present.
Docs: https://lodash.com/docs/4.17.15#get

Suggested change
const firstRow = (get(this.data, this.element.config.name) || [{}])[0];
const firstRow = get(this.data, this.element.config.name, [{}][0]);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, but I use || [{}] to cover the case in which get(this.data, this.element.config.name) is defined but is null

await ValidationsFactory(this.element.items, { screen: this.screen, data: {_parent: this.data, ...firstRow } }).addValidations(loopField['$each']);
}
}
Expand All @@ -98,6 +122,10 @@ class FormLoopValidations extends Validations {
*/
class FormMultiColumnValidations extends Validations {
async addValidations(validations) {
// Disable validations if field is hidden
if (!this.isVisible()) {
return;
}
await ValidationsFactory(this.element.items, { screen: this.screen, data: this.data }).addValidations(validations);
}
}
Expand All @@ -107,6 +135,10 @@ class FormMultiColumnValidations extends Validations {
*/
class PageNavigateValidations extends Validations {
async addValidations(validations) {
// Disable validations if field is hidden
if (!this.isVisible()) {
return;
}
if (this.screen.pagesValidated && !this.screen.pagesValidated.includes(parseInt(this.element.config.eventData))) {
this.screen.pagesValidated.push(parseInt(this.element.config.eventData));
if (this.screen.config[this.element.config.eventData] && this.screen.config[this.element.config.eventData].items) {
Expand Down Expand Up @@ -168,6 +200,7 @@ class FormElementValidations extends Validations {
validation.configs.forEach((cnf) => {
params.push(cnf.value);
});
params.push(fieldName);
validationFn = validationFn(...params);
}
fieldValidation[rule] = validationFn;
Expand Down
5 changes: 4 additions & 1 deletion src/mixins/Json2Vue.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,10 @@ export default {
component.methods.loadValidationRules = function() {
// Asynchronous loading of validations
const validations = {};
ValidationsFactory(definition, { screen: definition, firstPage, data: this.vdata }).addValidations(validations).then(() => {
ValidationsFactory(definition, { screen: definition, firstPage, data: {_parent: this._parent, ...this.vdata} }).addValidations(validations).then(() => {
if (_.isEqual(this.ValidationRules__, validations)) {
return;
}
this.ValidationRules__ = validations;
this.$nextTick(() => {
if (this.$v) {
Expand Down
44 changes: 44 additions & 0 deletions src/mixins/ScreenBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,50 @@ export default {
},
},
methods: {
getDataAccordingToFieldLevel(dataWithParent, level) {
if (level === 0 || !dataWithParent) {
return dataWithParent;
}
return this.getDataAccordingToFieldLevel(dataWithParent._parent, level - 1);
},
addReferenceToParents(data) {
if (!data) {
return;
}
const parent = this.addReferenceToParents(this.findParent(data));
return {
_parent: parent,
...data,
};
},
findParent(child, data = this.vdata, parent = this._parent) {
if (child === data) {
return parent;
}
for (const key in data) {
if (data[key] instanceof Array) {
for (const item of data[key]) {
const result = this.findParent(child, item, data);
if (result) {
return result;
}
}
} else if (data[key] instanceof Object) {
const found = this.findParent(child, data[key], data);
if (found) {
return found;
}
} else {
if (child === data[key]) {
return parent;
}
}
}
},
getRootScreen(screen = this) {
const parentScreen = screen.$parent.$parent;
return parentScreen && parentScreen.getRootScreen instanceof Function ? parentScreen.getRootScreen(parentScreen) : screen;
},
tryFormField(variableName, callback, defaultValue = null) {
try {
return callback();
Expand Down
39 changes: 25 additions & 14 deletions src/mixins/ValidationRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ export const custom_date = (date) => {
return checkDate.isValid();
};

export const after = (after) => helpers.withParams({after}, function(date) {
export const after = (after, fieldName) => helpers.withParams({after}, function(date, data) {
// Get check date
const dataWithParent = {today: moment().format('YYYY-MM-DD'), _parent: this._parent, ...this.vdata};
const level = fieldName.split('.').length - 1;
const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
dataWithParent.today = moment().format('YYYY-MM-DD');
const checkDate = moment(get(dataWithParent, after, after));
if (!checkDate.isValid()) {
return false;
Expand All @@ -81,9 +83,11 @@ export const after = (after) => helpers.withParams({after}, function(date) {
return inputDate > afterDate;
});

export const after_or_equal = (after_or_equal) => helpers.withParams({after_or_equal}, function(date) {
export const after_or_equal = (after_or_equal, fieldName) => helpers.withParams({after_or_equal}, function(date, data) {
// Get check date
const dataWithParent = {today: moment().format('YYYY-MM-DD'), _parent: this._parent, ...this.vdata};
const level = fieldName.split('.').length - 1;
const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
dataWithParent.today = moment().format('YYYY-MM-DD');
const checkDate = moment(get(dataWithParent, after_or_equal, after_or_equal));
if (!checkDate.isValid()) {
return false;
Expand All @@ -94,9 +98,11 @@ export const after_or_equal = (after_or_equal) => helpers.withParams({after_or_e
return inputDate >= equalOrAfterDate;
});

export const before = (before) => helpers.withParams({before}, function(date) {
export const before = (before, fieldName) => helpers.withParams({before}, function(date, data) {
// Get check date
const dataWithParent = {today: moment().format('YYYY-MM-DD'), _parent: this._parent, ...this.vdata};
const level = fieldName.split('.').length - 1;
const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
dataWithParent.today = moment().format('YYYY-MM-DD');
const checkDate = moment(get(dataWithParent, before, before));
if (!checkDate.isValid()) {
return false;
Expand All @@ -107,9 +113,11 @@ export const before = (before) => helpers.withParams({before}, function(date) {
return inputDate < beforeDate;
});

export const before_or_equal = (before_or_equal) => helpers.withParams({before_or_equal}, function(date) {
export const before_or_equal = (before_or_equal, fieldName) => helpers.withParams({before_or_equal}, function(date, data) {
// Get check date
const dataWithParent = {today: moment().format('YYYY-MM-DD'), _parent: this._parent, ...this.vdata};
const level = fieldName.split('.').length - 1;
const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
dataWithParent.today = moment().format('YYYY-MM-DD');
const checkDate = moment(get(dataWithParent, before_or_equal, before_or_equal));
if (!checkDate.isValid()) {
return false;
Expand Down Expand Up @@ -150,8 +158,9 @@ export const required = (value) => {
return value instanceof Array ? value.length > 0 : !!value;
};

export const requiredIf = (variable, expected) => helpers.withParams({variable, expected}, function(value) {
const dataWithParent = {_parent: this._parent, ...this.vdata};
export const requiredIf = (variable, expected, fieldName) => helpers.withParams({variable, expected}, function(value, data) {
const level = fieldName.split('.').length - 1;
const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
const variableValue = get(dataWithParent, variable);
const isBoolean = (variableValue === true || variableValue === false);
let expectedValue = expected;
Expand All @@ -162,8 +171,9 @@ export const requiredIf = (variable, expected) => helpers.withParams({variable,
return value instanceof Array ? value.length > 0 : !!value;
});

export const requiredUnless = (variable, expected) => helpers.withParams({variable, expected}, function(value) {
const dataWithParent = {_parent: this._parent, ...this.vdata};
export const requiredUnless = (variable, expected, fieldName) => helpers.withParams({variable, expected}, function(value, data) {
const level = fieldName.split('.').length - 1;
const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
const variableValue = get(dataWithParent, variable);
const isBoolean = (variableValue === true || variableValue === false);
let expectedValue = expected;
Expand All @@ -174,8 +184,9 @@ export const requiredUnless = (variable, expected) => helpers.withParams({variab
return value instanceof Array ? value.length > 0 : !!value;
});

export const sameAs = (field) => helpers.withParams({field}, function(value) {
const dataWithParent = {_parent: this._parent, ...this.vdata};
export const sameAs = (field, fieldName) => helpers.withParams({field}, function(value, data) {
const level = fieldName.split('.').length - 1;
const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
const valueSameAs = get(dataWithParent, field);
return value == valueSameAs;
});
Expand Down
19 changes: 18 additions & 1 deletion src/mixins/VisibilityRule.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import { Parser } from 'expr-eval';
import { debounce } from 'lodash';

export default {
mounted() {
this.refreshValidationRulesByName = debounce(this.refreshValidationRulesByName, 300);

this.$root.$on('refresh-validation-rules', () => {
this.loadValidationRules();
});
},
methods: {
refreshValidationRulesByName(fieldName, isVisible) {
if (fieldName) {
// Update the array of hidden fields
const fieldExists = this.hiddenFields__.indexOf(fieldName) !== -1;
if (isVisible && fieldExists) {
this.hiddenFields__ = this.hiddenFields__.filter((f) => f !== fieldName);
} else if (!isVisible && !fieldExists) {
this.hiddenFields__.push(fieldName);
}
}
this.$root.$emit('refresh-validation-rules');
},

visibilityRuleIsVisible(rule, fieldName) {
try {
const data = Object.assign({ _parent: this._parent }, this.vdata);
const isVisible = !!Parser.evaluate(rule, Object.assign({}, this, data));
const isVisible = !!Parser.evaluate(rule, Object.assign({}, data));

// Update the array of hidden fields
if (fieldName) {
Expand All @@ -25,6 +41,7 @@ export default {
}


this.refreshValidationRulesByName(fieldName, isVisible);
return isVisible;
} catch (e) {
return false;
Expand Down
Loading