Skip to content

Commit

Permalink
Features/7001 question isready (#7002)
Browse files Browse the repository at this point in the history
* Implement the base refactoring #7001

* Support valueName (related questions) in isReady #7001

* Include async function in expression execution into isReady #7001

* Support isReady for carry-foward, #7001

* FIx the issue with carry-forward & choicesfromurl & predefined values #7001
  • Loading branch information
andrewtelnov committed Sep 25, 2023
1 parent 87b630b commit 0782d1d
Show file tree
Hide file tree
Showing 16 changed files with 393 additions and 100 deletions.
2 changes: 1 addition & 1 deletion src/base-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export interface ISurvey extends ITextProcessor, ISurveyErrorOwner {
validateQuestion(question: IQuestion): SurveyError;
validatePanel(panel: IPanel): SurveyError;
hasVisibleQuestionByValueName(valueName: string): boolean;
questionCountByValueName(valueName: string): number;
questionsByValueName(valueName: string): Array<IQuestion>;
processHtml(html: string, reason: string): string;
getSurveyMarkdownHtml(element: Base, text: string, name: string): string;
getRendererForString(element: Base, name: string): string;
Expand Down
29 changes: 28 additions & 1 deletion src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,14 +715,41 @@ export class Base {
if(!expression) return;
if(!!info.canRun && !info.canRun(this)) return;
if(!info.runner) {
info.runner = new ExpressionRunner(expression);
info.runner = this.createExpressionRunner(expression);
info.runner.onRunComplete = (res: any) => {
info.onExecute(this, res);
};
}
info.runner.expression = expression;
info.runner.run(values, properties);
}
private asynExpressionHash: any;
private doBeforeAsynRun(id: number): void {
if(!this.asynExpressionHash) this.asynExpressionHash = [];
const isChanged = !this.isAsyncExpressionRunning;
this.asynExpressionHash[id] = true;
if(isChanged) {
this.onAsyncRunningChanged();
}
}
private doAfterAsynRun(id: number): void {
if(!!this.asynExpressionHash) {
delete this.asynExpressionHash[id];
if(!this.isAsyncExpressionRunning) {
this.onAsyncRunningChanged();
}
}
}
protected onAsyncRunningChanged(): void {}
public get isAsyncExpressionRunning(): boolean {
return !!this.asynExpressionHash && Object.keys(this.asynExpressionHash).length > 0;
}
protected createExpressionRunner(expression: string): ExpressionRunner {
const res = new ExpressionRunner(expression);
res.onBeforeAsyncRun = (id: number): void => { this.doBeforeAsynRun(id); };
res.onAfterAsyncRun = (id: number): void => { this.doAfterAsynRun(id); };
return res;
}
/**
* Registers a function to call when a property value changes.
* @param propertyNames An array of one or multiple property names.
Expand Down
16 changes: 15 additions & 1 deletion src/conditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,15 @@ export class ExpressionExecutor implements IExpresionExecutor {

export class ExpressionRunnerBase {
private expressionExecutor: IExpresionExecutor;
private static IdCounter = 1;
private _id: number = ExpressionRunnerBase.IdCounter ++;
public onBeforeAsyncRun: (id: number) => void;
public onAfterAsyncRun: (id: number) => void;

public constructor(expression: string) {
this.expression = expression;
}
public get id(): number { return this._id; }
public get expression(): string {
return !!this.expressionExecutor ? this.expressionExecutor.expression : "";
}
Expand Down Expand Up @@ -156,9 +161,16 @@ export class ExpressionRunnerBase {
values: HashTable<any>,
properties: HashTable<any> = null
): any {
if(this.onBeforeAsyncRun && this.isAsync) {
this.onBeforeAsyncRun(this.id);
}
return this.expressionExecutor.run(values, properties);
}
protected doOnComplete(res: any): void {}
protected doOnComplete(res: any): void {
if(this.onAfterAsyncRun && this.isAsync) {
this.onAfterAsyncRun(this.id);
}
}
}

export class ConditionRunner extends ExpressionRunnerBase {
Expand All @@ -171,6 +183,7 @@ export class ConditionRunner extends ExpressionRunnerBase {
}
protected doOnComplete(res: any): void {
if (!!this.onRunComplete) this.onRunComplete(res == true);
super.doOnComplete(res);
}
}

Expand All @@ -181,5 +194,6 @@ export class ExpressionRunner extends ExpressionRunnerBase {
}
protected doOnComplete(res: any): void {
if (!!this.onRunComplete) this.onRunComplete(res);
super.doOnComplete(res);
}
}
63 changes: 57 additions & 6 deletions src/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class Question extends SurveyElement<Question>
onUpdateCssClassesCallback: (css: any) => void;
onGetSurvey: () => ISurvey;
private locProcessedTitle: LocalizableString;
protected isReadyValue: boolean = true;
private isReadyValue: boolean = true;
private commentElements: Array<HTMLElement>;
private dependedQuestions: Array<Question> = [];

Expand Down Expand Up @@ -218,21 +218,72 @@ export class Question extends SurveyElement<Question>
this.valueName ? this.valueName : oldValue
);
}
public set isReady(val: boolean) {
public get isReady(): boolean {
return this.isReadyValue;
}
protected onAsyncRunningChanged(): void {
this.updateIsReady();
}
protected updateIsReady(): void {
let res = this.getIsQuestionReady();
if(res) {
const questions = this.getIsReadyDependsOn();
for(let i = 0; i < questions.length; i ++) {
if(!questions[i].getIsQuestionReady()) {
res = false;
break;
}
}
}
this.setIsReady(res);
}
protected getIsQuestionReady(): boolean {
return !this.isAsyncExpressionRunning && this.getAreNestedQuestionsReady();
}
private getAreNestedQuestionsReady(): boolean {
const questions = this.getIsReadyNestedQuestions();
if(!Array.isArray(questions)) return true;
for(let i = 0; i < questions.length; i ++) {
if(!questions[i].isReady) return false;
}
return true;
}
protected getIsReadyNestedQuestions(): Array<Question> {
return this.getNestedQuestions();
}
private setIsReady(val: boolean): void {
const oldIsReady = this.isReadyValue;
this.isReadyValue = val;
if (oldIsReady != val) {
this.getIsReadyDependends().forEach(q => q.updateIsReady());
this.onReadyChanged.fire(this, {
question: this,
isReady: val,
oldIsReady: oldIsReady,
});
}
}
public get isReady(): boolean {
return this.isReadyValue;
protected getIsReadyDependsOn(): Array<Question> {
return this.getIsReadyDependendCore(true);
}
private getIsReadyDependends(): Array<Question> {
return this.getIsReadyDependendCore(false);
}
private getIsReadyDependendCore(isDependOn: boolean): Array<Question> {
if(!this.survey) return [];
const questions = this.survey.questionsByValueName(this.getValueName());
const res = new Array<Question>();
questions.forEach(q => { if(q !== this) res.push(<Question>q); });
if(!isDependOn) {
if(this.parentQuestion) {
res.push(this.parentQuestion);
}
if(this.dependedQuestions.length > 0) {
this.dependedQuestions.forEach(q => res.push(q));
}
}
return res;
}

public choicesLoaded(): void { }
/**
* Returns a page to which the question belongs and allows you to move this question to a different page.
Expand Down Expand Up @@ -1646,7 +1697,7 @@ export class Question extends SurveyElement<Question>
}
protected getDefaultRunner(runner: ExpressionRunner, expression: string): ExpressionRunner {
if (!runner && !!expression) {
runner = new ExpressionRunner(expression);
runner = this.createExpressionRunner(expression);
}
if (!!runner) {
runner.expression = expression;
Expand Down
29 changes: 22 additions & 7 deletions src/question_baseselect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,14 +573,17 @@ export class QuestionSelectBase extends Question {
if(!val) return false;
return this.hasUnknownValue(val, true, false);
}
protected getIsQuestionReady(): boolean {
return super.getIsQuestionReady() && !this.waitingChoicesByURL && !this.waitingGetChoiceDisplayValueResponse;
}
protected updateSelectedItemValues(): void {
if(this.waitingGetChoiceDisplayValueResponse || !this.survey || this.isEmpty()) return;
const value = this.value;
const valueArray: Array<any> = Array.isArray(value) ? value : [value];
const hasItemWithoutValues = valueArray.some(val => !ItemValue.getItemByValue(this.choices, val));
if (hasItemWithoutValues) {
this.waitingGetChoiceDisplayValueResponse = true;
this.isReady = !this.waitingAcyncOperations;
this.updateIsReady();
this.survey.getChoiceDisplayValue({
question: this,
values: valueArray,
Expand All @@ -595,7 +598,7 @@ export class QuestionSelectBase extends Question {
else {
this.selectedItemValues = items[0];
}
this.isReady = !this.waitingAcyncOperations;
this.updateIsReady();
}
});
}
Expand Down Expand Up @@ -1036,13 +1039,24 @@ export class QuestionSelectBase extends Question {
this.setCarryForwardQuestionType(!!selBaseQuestion, !!arrayQuestion);
return !!selBaseQuestion || !!arrayQuestion ? question : null;
}
protected getIsReadyDependsOn(): Array<Question> {
const res = super.getIsReadyDependsOn();
if(this.carryForwardQuestion) {
res.push(this.carryForwardQuestion);
}
return res;
}
private getQuestionWithChoices(): QuestionSelectBase {
return this.getQuestionWithChoicesCore(this.findCarryForwardQuestion());
}
private carryForwardQuestion: Question;
private findCarryForwardQuestion(data?: ISurveyData): Question {
if(!data) data = this.data;
if (!this.choicesFromQuestion || !data) return null;
return <Question>data.findQuestionByName(this.choicesFromQuestion);
this.carryForwardQuestion = null;
if (this.choicesFromQuestion && data) {
this.carryForwardQuestion = <Question>data.findQuestionByName(this.choicesFromQuestion);
}
return this.carryForwardQuestion;
}
private getQuestionWithChoicesCore(question: Question): QuestionSelectBase {
if(!!question && !!question.visibleChoices && (Serializer.isDescendantOf(question.getType(), "selectbase")) && question !== this)
Expand Down Expand Up @@ -1256,7 +1270,7 @@ export class QuestionSelectBase extends Question {
: this.textProcessor;
if (!processor) processor = this.survey;
if (!processor) return;
this.isReadyValue = !this.waitingAcyncOperations;
this.updateIsReady();
this.isRunningChoices = true;
this.choicesByUrl.run(processor);
this.isRunningChoices = false;
Expand Down Expand Up @@ -1432,9 +1446,10 @@ export class QuestionSelectBase extends Question {
}
public clearIncorrectValues() {
if (!this.hasValueToClearIncorrectValues()) return;
if(this.carryForwardQuestion && !this.carryForwardQuestion.isReady) return;
if (
!!this.survey &&
this.survey.questionCountByValueName(this.getValueName()) > 1
this.survey.questionsByValueName(this.getValueName()).length > 1
)
return;
if (
Expand Down Expand Up @@ -1628,7 +1643,7 @@ export class QuestionSelectBase extends Question {

public choicesLoaded(): void {
this.isChoicesLoaded = true;
this.isReady = !this.waitingAcyncOperations;
this.updateIsReady();
if (this.survey) {
this.survey.loadedChoicesFromServer(this);
}
Expand Down
2 changes: 1 addition & 1 deletion src/question_checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase {
protected convertValueToObject(val: any): any {
if (!this.valuePropertyName) return val;
let dest = undefined;
if (!!this.survey && this.survey.questionCountByValueName(this.getValueName()) > 1) {
if (!!this.survey && this.survey.questionsByValueName(this.getValueName()).length > 1) {
dest = this.data.getValue(this.getValueName());
}
return Helpers.convertArrayValueToObject(val, this.valuePropertyName, dest);
Expand Down
16 changes: 10 additions & 6 deletions src/question_expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class QuestionExpressionModel extends Question {
this.createLocalizableString("format", this);
this.registerPropertyChangedHandlers(["expression"], () => {
if (this.expressionRunner) {
this.expressionRunner = new ExpressionRunner(this.expression);
this.expressionRunner = this.createRunner();
}
});
this.registerPropertyChangedHandlers(["format", "currency", "displayStyle"], () => {
Expand Down Expand Up @@ -71,12 +71,8 @@ export class QuestionExpressionModel extends Question {
return;
this.locCalculation();
if (!this.expressionRunner) {
this.expressionRunner = new ExpressionRunner(this.expression);
this.expressionRunner = this.createRunner();
}
this.expressionRunner.onRunComplete = (newValue) => {
this.value = this.roundValue(newValue);
this.unlocCalculation();
};
this.expressionRunner.run(values, properties);
}
protected canCollectErrors(): boolean {
Expand All @@ -85,6 +81,14 @@ export class QuestionExpressionModel extends Question {
protected hasRequiredError(): boolean {
return false;
}
private createRunner(): ExpressionRunner {
const res = this.createExpressionRunner(this.expression);
res.onRunComplete = (newValue) => {
this.value = this.roundValue(newValue);
this.unlocCalculation();
};
return res;
}
/**
* The maximum number of fraction digits. Applies only if the `displayStyle` property is not `"none"`. Accepts values in the range from -1 to 20, where -1 disables the property.
*
Expand Down
13 changes: 11 additions & 2 deletions src/question_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,22 +441,31 @@ export class QuestionFileModel extends Question {
if (!!this._previewLoader) {
this._previewLoader.dispose();
}
this.isReadyValue = false;
this.isFileLoading = true;
this._previewLoader = new FileLoader(this, (status, loaded) => {
if (status === "loaded") {
loaded.forEach((val) => {
this.previewValue.push(val);
});
this.previewValueChanged();
}
this.isReady = true;
this.isFileLoading = false;
this._previewLoader.dispose();
this._previewLoader = undefined;
});
this._previewLoader.load(newValues);
}
this.previewValueChanged();
}
private isFileLoadingValue: boolean;
protected get isFileLoading(): boolean { return this.isFileLoadingValue; }
protected set isFileLoading(val: boolean) {
this.isFileLoadingValue = val;
this.updateIsReady();
}
protected getIsQuestionReady(): boolean {
return super.getIsQuestionReady() && !this.isFileLoading;
}
protected onCheckForErrors(
errors: Array<SurveyError>,
isOnValueChanged: boolean
Expand Down
13 changes: 12 additions & 1 deletion src/question_matrixdropdownbase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1797,8 +1797,19 @@ export class QuestionMatrixDropdownModelBase extends QuestionMatrixBaseModel<Mat
}
}
}
protected getIsReadyNestedQuestions(): Array<Question> {
if(!this.generatedVisibleRows) return [];
const res = new Array<Question>();
this.collectNestedQuestonsInRows(this.generatedVisibleRows, res, false);
if(!!this.generatedTotalRow) {
this.collectNestedQuestonsInRows([this.generatedTotalRow], res, false);
}
return res;
}
protected collectNestedQuestionsCore(questions: Question[], visibleOnly: boolean): void {
const rows = this.visibleRows;
this.collectNestedQuestonsInRows(this.visibleRows, questions, visibleOnly);
}
protected collectNestedQuestonsInRows(rows: Array<MatrixDropdownRowModelBase>, questions: Question[], visibleOnly: boolean): void {
rows.forEach(row => {
row.questions.forEach(q => q.collectNestedQuestions(questions, visibleOnly));
});
Expand Down
Loading

0 comments on commit 0782d1d

Please sign in to comment.