Skip to content

Commit

Permalink
Features 6989 resetValueIf (#6991)
Browse files Browse the repository at this point in the history
* Add "clearValueIf" expression into question #6989

* Add tests on matrices #6869

* Change clearValueIf into question trigger clearValueOn #6989

* Support root question in panels/matrices expressions #6989

* Rename clearValueOn to resetValueIf #6989

* Add a description

* rename private properties #6989

---------

Co-authored-by: Roman Tsukanov <roman.tsukanov@devexpress.com>
  • Loading branch information
andrewtelnov and Roman Tsukanov committed Sep 25, 2023
1 parent 2fada3d commit ed99e06
Show file tree
Hide file tree
Showing 11 changed files with 405 additions and 57 deletions.
24 changes: 24 additions & 0 deletions src/conditionProcessValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,30 @@ export class ProcessValue {
valueInfo.path = res.hasValue ? res.path : null;
valueInfo.sctrictCompare = res.sctrictCompare;
}
public isAnyKeyChanged(keys: any, usedNames: string[]): boolean {
for (var i = 0; i < usedNames.length; i++) {
var name = usedNames[i];
if (keys.hasOwnProperty(name)) return true;
var firstName = this.getFirstName(name);
if (!keys.hasOwnProperty(firstName)) continue;
if (name === firstName) return true;
var keyValue = keys[firstName];
if (keyValue == undefined) continue;
if (
!keyValue.hasOwnProperty("oldValue") ||
!keyValue.hasOwnProperty("newValue")
)
return true;
var v: any = {};
v[firstName] = keyValue["oldValue"];
var oldValue = this.getValue(name, v);
v[firstName] = keyValue["newValue"];
var newValue = this.getValue(name, v);
if(!Helpers.isTwoValueEquals(oldValue, newValue, false, false, false)) return true;
}
return false;

}
private getValueFromPath(path: Array<string | number>, values: any): any {
if(path.length === 2 && path[0] === surveyBuiltInVarible) {
return this.getValueFromSurvey(<string>path[1]);
Expand Down
14 changes: 12 additions & 2 deletions src/conditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export class ExpressionExecutor implements IExpresionExecutor {

export class ExpressionRunnerBase {
private expressionExecutor: IExpresionExecutor;
private variables: string[];
private containsFunc: boolean;
private static IdCounter = 1;
private _id: number = ExpressionRunnerBase.IdCounter ++;
public onBeforeAsyncRun: (id: number) => void;
Expand All @@ -141,14 +143,22 @@ export class ExpressionRunnerBase {
if(!!this.expressionExecutor && value === this.expression) return;
this.expressionExecutor = ExpressionExecutor.createExpressionExecutor(value);
this.expressionExecutor.onComplete = (res: any) => { this.doOnComplete(res); };
this.variables = undefined;
this.containsFunc = undefined;
}

public getVariables(): Array<string> {
return this.expressionExecutor.getVariables();
if(this.variables === undefined) {
this.variables = this.expressionExecutor.getVariables();
}
return this.variables;
}

public hasFunction(): boolean {
return this.expressionExecutor.hasFunction();
if(this.containsFunc === undefined) {
this.containsFunc = this.expressionExecutor.hasFunction();
}
return this.containsFunc;
}
public get isAsync(): boolean {
return this.expressionExecutor.isAsync;
Expand Down
42 changes: 42 additions & 0 deletions src/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { CssClassBuilder } from "./utils/cssClassBuilder";
import { getElementWidth, increaseHeightByContent, isContainerVisible } from "./utils/utils";
import { PopupModel } from "./popup";
import { ConsoleWarnings } from "./console-warnings";
import { ProcessValue } from "./conditionProcessValue";

export interface IConditionObject {
name: string;
Expand Down Expand Up @@ -505,6 +506,29 @@ export class Question extends SurveyElement<Question>
requiredAnsweredQuestionCount: !this.isEmpty() && this.isRequired ? 1 : 0,
};
}
private resetValueIfExpression: ExpressionRunner;
private isRunningResetValueIf: boolean;
public runTriggers(name: string, value: any): void {
if(this.isRunningResetValueIf || !this.isVisible || this.isReadOnly || !this.resetValueIf || this.isEmpty()) return;
if(this.parentQuestion && this.parentQuestion.getValueName() === name) return;
if(!this.resetValueIfExpression) {
this.resetValueIfExpression = new ExpressionRunner(this.resetValueIf);
this.resetValueIfExpression.onRunComplete = (res: any): void => {
this.isRunningResetValueIf = false;
if(res === true) {
this.clearValue();
this.updateValueWithDefaults();
}
};
} else {
this.resetValueIfExpression.expression = this.resetValueIf;
}
const keys: any = {};
keys[name] = value;
if(!new ProcessValue().isAnyKeyChanged(keys, this.resetValueIfExpression.getVariables())) return;
this.isRunningResetValueIf = true;
this.resetValueIfExpression.run(this.getDataFilteredValues(), this.getDataFilteredProperties());
}
private runConditions() {
if (this.data && !this.isLoadingFromJson) {
if (!this.isDesignMode) {
Expand Down Expand Up @@ -1427,6 +1451,7 @@ export class Question extends SurveyElement<Question>
if (!!this.comment) {
this.comment = undefined;
}
this.isValueChangedDirectly = false;
}
public unbindValue(): void {
this.clearValue();
Expand Down Expand Up @@ -1575,6 +1600,19 @@ export class Question extends SurveyElement<Question>
this.defaultValueRunner = undefined;
this.updateValueWithDefaults();
}
/**
* A Boolean expression. If it evaluates to `true`, the question value is reset to [default](#defaultValue).
*
* A survey parses and runs all expressions on startup. If any values used in the expression change, the survey re-evaluates it.
*
* [Expressions](https://surveyjs.io/form-library/documentation/design-survey/conditional-logic#expressions (linkStyle))
*/
public get resetValueIf(): string {
return this.getPropertyValue("resetValueIf");
}
public set resetValueIf(val: string) {
this.setPropertyValue("resetValueIf", val);
}
public get resizeStyle() {
return this.allowResizeComment ? "both" : "none";
}
Expand Down Expand Up @@ -2557,6 +2595,10 @@ Serializer.addClass("question", [
},
{ name: "valueName", onSettingValue: (obj: any, val: any): any => { return makeNameValid(val); } },
"enableIf:condition",
{
name: "resetValueIf:condition",
category: "logic", visible: false
},
"defaultValue:value",
{
name: "defaultValueExpression:expression",
Expand Down
41 changes: 24 additions & 17 deletions src/question_matrixdropdownbase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface IMatrixDropdownData {
): Question;
onTotalValueChanged(): any;
getSurvey(): ISurvey;
getDataFilteredValues(): any;
}

export class MatrixDropdownCell {
Expand Down Expand Up @@ -341,14 +342,15 @@ implements ISurveyData, ISurveyImpl, ILocalizableOwner {
return this.value;
}
getFilteredValues(): any {
var allValues = this.getAllValues();
const res = this.data ? this.data.getDataFilteredValues() : {};
var values: any = this.validationValues;
if(!values) values = {};
values.row = allValues;
for (var key in allValues) {
values[key] = allValues[key];
if(values) {
for (var key in values) {
res[key] = values[key];
}
}
return values;
res.row = this.getAllValues();
return res;
}
getFilteredProperties(): any {
return { survey: this.getSurvey(), row: this };
Expand Down Expand Up @@ -441,6 +443,9 @@ implements ISurveyData, ISurveyImpl, ILocalizableOwner {
const isDeleting = newColumnValue == null && !changedQuestion ||
isComment && !newColumnValue && !!changedQuestion && changedQuestion.autoOtherMode;
this.data.onRowChanged(this, changedName, newValue, isDeleting);
if(changedName) {
this.runTriggers(MatrixDropdownTotalRowModel.RowVariableName + "." + changedName, newValue);
}
this.onAnyValueChanged(MatrixDropdownRowModelBase.RowVariableName, "");
}

Expand All @@ -466,7 +471,10 @@ implements ISurveyData, ISurveyImpl, ILocalizableOwner {
}
this.isSettingValue = false;
}

public runTriggers(name: string, value: any): void {
if(!name) return;
this.questions.forEach(q => q.runTriggers(name, value));
}
private hasQuestonError(question: Question): boolean {
if (!question) return false;
if (
Expand Down Expand Up @@ -1364,6 +1372,10 @@ export class QuestionMatrixDropdownModelBase extends QuestionMatrixBaseModel<Mat
counter < 3
);
}
public runTriggers(name: string, value: any): void {
super.runTriggers(name, value);
this.runFuncForCellQuestions((q: Question) => { q.runTriggers(name, value); });
}
protected shouldRunColumnExpression(): boolean {
return false;
}
Expand Down Expand Up @@ -2143,23 +2155,15 @@ export class QuestionMatrixDropdownModelBase extends QuestionMatrixBaseModel<Mat
column: this.getColumnByName(columnName)
};
}
protected onCellValueChanged(
row: MatrixDropdownRowModelBase,
columnName: string,
rowValue: any
) {
protected onCellValueChanged(row: MatrixDropdownRowModelBase, columnName: string, rowValue: any): void {
if (!this.survey) return;
var options = this.getOnCellValueChangedOptions(row, columnName, rowValue);
if (!!this.onCellValueChangedCallback) {
this.onCellValueChangedCallback(options);
}
this.survey.matrixCellValueChanged(this, options);
}
validateCell(
row: MatrixDropdownRowModelBase,
columnName: string,
rowValue: any
): SurveyError {
validateCell(row: MatrixDropdownRowModelBase, columnName: string, rowValue: any): SurveyError {
if (!this.survey) return;
var options = this.getOnCellValueChangedOptions(row, columnName, rowValue);
return this.survey.matrixCellValidate(this, options);
Expand Down Expand Up @@ -2363,6 +2367,9 @@ export class QuestionMatrixDropdownModelBase extends QuestionMatrixBaseModel<Mat
);
}
}
getDataFilteredValues(): any {
return this.data ? this.data.getFilteredValues(): {};
}
getParentTextProcessor(): ITextProcessor {
if (!this.parentQuestion || !this.parent) return null;
const data = (<any>this.parent).data;
Expand Down
12 changes: 12 additions & 0 deletions src/question_matrixdropdowncolumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,18 @@ export class MatrixDropdownColumn extends Base
public set requiredIf(val: string) {
this.templateQuestion.requiredIf = val;
}
public get resetValueIf(): string {
return this.templateQuestion.resetValueIf;
}
public set resetValueIf(val: string) {
this.templateQuestion.resetValueIf = val;
}
public get defaultValueExpression(): string {
return this.templateQuestion.defaultValueExpression;
}
public set defaultValueExpression(val: string) {
this.templateQuestion.defaultValueExpression = val;
}
public get isUnique(): boolean {
return this.getPropertyValue("isUnique");
}
Expand Down
20 changes: 15 additions & 5 deletions src/question_paneldynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,19 @@ export class QuestionPanelDynamicItem implements ISurveyData, ISurveyImpl {
var values = this.getAllValues();
return values[name];
}
public setValue(name: string, newValue: any) {
public setValue(name: string, newValue: any): void {
const oldItemData = this.data.getPanelItemData(this);
const oldValue = !!oldItemData ? oldItemData[name] : undefined;
if (Helpers.isTwoValueEquals(newValue, oldValue, false, true, false)) return;
this.data.setPanelItemData(this, name, Helpers.getUnbindValue(newValue));
const questions = this.panel.questions;
const triggerName = QuestionPanelDynamicItem.ItemVariableName + "." + name;
for (var i = 0; i < questions.length; i++) {
if (questions[i].getValueName() === name) continue;
questions[i].checkBindings(name, newValue);
const q = questions[i];
if (q.getValueName() !== name) {
q.checkBindings(name, newValue);
}
q.runTriggers(triggerName, newValue);
}
}
getVariable(name: string): any {
Expand Down Expand Up @@ -1428,8 +1432,8 @@ export class QuestionPanelDynamicModel extends Question
for (var i = 0; i < panelObjs.length; i++) {
if (panelObjs[i].question == context) continue;
const obj: IConditionObject = {
name: prefixName + "panel." + panelObjs[i].name,
text: prefixText + "panel." + panelObjs[i].text,
name: prefixName + QuestionPanelDynamicItem.ItemVariableName + "." + panelObjs[i].name,
text: prefixText + QuestionPanelDynamicItem.ItemVariableName + "." + panelObjs[i].text,
question: panelObjs[i].question
};
if (context === true) {
Expand Down Expand Up @@ -1514,6 +1518,12 @@ export class QuestionPanelDynamicModel extends Question
super.runCondition(values, properties);
this.runPanelsCondition(this.panels, values, properties);
}
public runTriggers(name: string, value: any): void {
super.runTriggers(name, value);
this.visiblePanels.forEach(p => {
p.questions.forEach(q => q.runTriggers(name, value));
});
}
private reRunCondition() {
if (!this.data) return;
this.runCondition(
Expand Down
8 changes: 7 additions & 1 deletion src/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5618,6 +5618,7 @@ export class SurveyModel extends SurveyElementCore
this.isValueChangedOnRunningCondition = true;
} else {
this.runConditions();
this.runQuestionsTriggers(name, value);
}
}
private runConditionsCore(properties: any) {
Expand All @@ -5633,10 +5634,15 @@ export class SurveyModel extends SurveyElementCore
);
}
super.runConditionCore(this.conditionValues, properties);
for (var i = 0; i < pages.length; i++) {
for (let i = 0; i < pages.length; i++) {
pages[i].runCondition(this.conditionValues, properties);
}
}
private runQuestionsTriggers(name: string, value: any): void {
if(this.isDisplayMode || this.isDesignMode) return;
const questions = this.getAllQuestions(true);
questions.forEach(q => q.runTriggers(name, value));
}
private checkIfNewPagesBecomeVisible(oldCurrentPageIndex: number) {
var newCurrentPageIndex = this.pages.indexOf(this.currentPage);
if (newCurrentPageIndex <= oldCurrentPageIndex + 1) return;
Expand Down
36 changes: 4 additions & 32 deletions src/trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,9 @@ export class Trigger extends Base {
return Trigger.operatorsValue;
}
private conditionRunner: ConditionRunner;
private usedNames: Array<string>;
private hasFunction: boolean;
private idValue: number = (Trigger.idCounter ++);
constructor() {
super();
this.usedNames = [];
this.registerPropertyChangedHandlers(["operator", "value", "name"], () => {
this.oldPropertiesChanged();
});
Expand Down Expand Up @@ -160,8 +157,6 @@ export class Trigger extends Base {
this.onExpressionChanged();
}
private onExpressionChanged() {
this.usedNames = [];
this.hasFunction = false;
this.conditionRunner = null;
}
public buildExpression(): string {
Expand All @@ -178,41 +173,18 @@ export class Trigger extends Base {
}
private isCheckRequired(keys: any): boolean {
if (!keys) return false;
this.buildUsedNames();
if (this.hasFunction === true) return true;
var processValue = new ProcessValue();
for (var i = 0; i < this.usedNames.length; i++) {
var name = this.usedNames[i];
if (keys.hasOwnProperty(name)) return true;
var firstName = processValue.getFirstName(name);
if (!keys.hasOwnProperty(firstName)) continue;
if (name === firstName) return true;
var keyValue = keys[firstName];
if (keyValue == undefined) continue;
if (
!keyValue.hasOwnProperty("oldValue") ||
!keyValue.hasOwnProperty("newValue")
)
return true;
var v: any = {};
v[firstName] = keyValue["oldValue"];
var oldValue = processValue.getValue(name, v);
v[firstName] = keyValue["newValue"];
var newValue = processValue.getValue(name, v);
if(!this.isTwoValueEquals(oldValue, newValue)) return true;
}
return false;
this.createConditionRunner();
if (this.conditionRunner.hasFunction() === true) return true;
return new ProcessValue().isAnyKeyChanged(keys, this.conditionRunner.getVariables());
}
private buildUsedNames() {
private createConditionRunner() {
if (!!this.conditionRunner) return;
var expression = this.expression;
if (!expression) {
expression = this.buildExpression();
}
if (!expression) return;
this.conditionRunner = new ConditionRunner(expression);
this.hasFunction = this.conditionRunner.hasFunction();
this.usedNames = this.conditionRunner.getVariables();
}
private get isRequireValue(): boolean {
return this.operator !== "empty" && this.operator != "notempty";
Expand Down

0 comments on commit ed99e06

Please sign in to comment.