Skip to content

Commit

Permalink
Introduce reactive hasError property into single matrix row fix #8193 (
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewtelnov committed Apr 29, 2024
1 parent c6c0656 commit b214283
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 44 deletions.
3 changes: 0 additions & 3 deletions src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,9 +566,6 @@ export class Base {
* @param val A new value for the property.
*/
public setPropertyValue(name: string, val: any): void {
if(name) {

}
if(!this.isLoadingFromJson) {
const prop = this.getPropertyByName(name);
if(!!prop) {
Expand Down
81 changes: 47 additions & 34 deletions src/question_matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export class MatrixRowModel extends Base {
this.registerPropertyChangedHandlers(["value"], () => {
if (this.data) this.data.onMatrixRowChanged(this);
});
if(this.data && this.data.hasErrorInRow(this)) {
this.hasError = true;
}
}
public get name(): string {
return this.item.value;
Expand All @@ -68,10 +71,16 @@ export class MatrixRowModel extends Base {
public get rowTextClasses(): string {
return new CssClassBuilder().append(this.data.cssClasses.rowTextCell).toString();
}
public get hasError(): boolean {
return this.getPropertyValue("hasError", false);
}
public set hasError(val: boolean) {
this.setPropertyValue("hasError", val);
}
public get rowClasses(): string {
const cssClasses = (<any>this.data).cssClasses;
return new CssClassBuilder().append(cssClasses.row)
.append(cssClasses.rowError, this.data.hasErrorInRow(this))
.append(cssClasses.rowError, this.hasError)
.append(cssClasses.rowReadOnly, this.isReadOnly)
.append(cssClasses.rowDisabled, this.data.isDisabledStyle)
.toString();
Expand Down Expand Up @@ -336,7 +345,7 @@ export class QuestionMatrixModel
return new CssClassBuilder()
.append(css.cell, hasCellText)
.append(hasCellText ? css.cellText : css.label)
.append(css.itemOnError, !hasCellText && (this.isAllRowRequired ? this.hasErrorInRow(row) : this.hasCssError()))
.append(css.itemOnError, !hasCellText && (this.isAllRowRequired || this.eachRowUnique ? row.hasError : this.hasCssError()))
.append(hasCellText ? css.cellTextSelected : css.itemChecked, isChecked)
.append(hasCellText ? css.cellTextDisabled : css.itemDisabled, this.isDisabledStyle)
.append(hasCellText ? css.cellTextReadOnly : css.itemReadOnly, this.isReadOnlyStyle)
Expand Down Expand Up @@ -458,68 +467,71 @@ export class QuestionMatrixModel
return loc ? loc : this.emptyLocalizableString;
}
supportGoNextPageAutomatic(): boolean {
return this.isMouseDown === true && this.hasValuesInAllRows(false);
return this.isMouseDown === true && this.hasValuesInAllRows();
}
private errorsInRow: HashTable<boolean>;
protected onCheckForErrors(errors: Array<SurveyError>, isOnValueChanged: boolean): void {
super.onCheckForErrors(errors, isOnValueChanged);
this.errorsInRow = undefined;
if (!isOnValueChanged || this.hasCssError()) {
if(this.hasErrorAllRowsRequired()) {
const rowsErrors = { noValue: false, isNotUnique: false };
this.checkErrorsAllRows(true, rowsErrors);
if(rowsErrors.noValue) {
errors.push(new RequiredInAllRowsError(null, this));
}
if(this.hasErrorEachRowUnique()) {
if(rowsErrors.isNotUnique) {
errors.push(new EachRowUniqueError(null, this));
}
}
}
private hasErrorAllRowsRequired(): boolean {
return this.isAllRowRequired && !this.hasValuesInAllRows(true);
}
private hasErrorEachRowUnique(): boolean {
return this.eachRowUnique && this.hasNonUniqueValueInRow();
private hasValuesInAllRows(): boolean {
const rowsErrors = { noValue: false, isNotUnique: false };
this.checkErrorsAllRows(false, rowsErrors, true);
return !rowsErrors.noValue;
}
private hasValuesInAllRows(addError: boolean): boolean {
private checkErrorsAllRows(modifyErrors: boolean, res: { noValue: boolean, isNotUnique: boolean }, allRowsRequired?: boolean): void {
var rows = this.generatedVisibleRows;
if (!rows) rows = this.visibleRows;
if (!rows) return true;
let res = true;
for (var i = 0; i < rows.length; i++) {
const row = rows[i];
const hasValue = !this.isValueEmpty(row.value);
if(addError && !hasValue) {
this.addErrorIntoRow(row);
}
res = res && hasValue;
if (!rows) return;
const rowsRequired = this.isAllRowRequired || allRowsRequired;
const rowsUnique = this.eachRowUnique;
res.noValue = false;
res.isNotUnique = false;
if(modifyErrors) {
this.errorsInRow = undefined;
}
return res;
}
private hasNonUniqueValueInRow(): boolean {
var rows = this.generatedVisibleRows;
if (!rows) rows = this.visibleRows;
if (!rows) return false;
if(!rowsRequired && !rowsUnique) return;
const hash: HashTable<any> = {};
let res = true;
for (var i = 0; i < rows.length; i++) {
const val = rows[i].value;
const isEmpty = this.isValueEmpty(val);
const isUnique = isEmpty || hash[val] !== true;
if(!isUnique) {
let isEmpty = this.isValueEmpty(val);
const isNotUnique = rowsUnique && (!isEmpty && hash[val] === true);
isEmpty = isEmpty && rowsRequired;
if(modifyErrors && (isEmpty || isNotUnique)) {
this.addErrorIntoRow(rows[i]);
}
res = res && isUnique;
if(!isEmpty) {
hash[val] = true;
}
res.noValue = res.noValue || isEmpty;
res.isNotUnique = res.isNotUnique || isNotUnique;
}
if(modifyErrors) {
rows.forEach(row => {
row.hasError = this.hasErrorInRow(row);
});
}
return !res;
}
private addErrorIntoRow(row: MatrixRowModel): void {
if(!this.errorsInRow) this.errorsInRow = {};
this.errorsInRow[row.name] = true;
row.hasError = true;
}
private refreshRowsErrors(): void {
if(!this.errorsInRow) return;
this.checkErrorsAllRows(true, { noValue: false, isNotUnique: false });
}
protected getIsAnswered(): boolean {
return super.getIsAnswered() && this.hasValuesInAllRows(false);
return super.getIsAnswered() && this.hasValuesInAllRows();
}
private createMatrixRow(
item: ItemValue,
Expand Down Expand Up @@ -548,6 +560,7 @@ export class QuestionMatrixModel
this.generatedVisibleRows[i].setValueDirectly(rowVal);
}
}
this.refreshRowsErrors();
this.updateIsAnswered();
this.isRowChanging = false;
}
Expand Down
25 changes: 18 additions & 7 deletions tests/question_matrix_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ QUnit.test("Matrix Question eachRowUnique property", function (assert) {
matrix.value = { row1: "col2", row2: "col2" };
assert.equal(matrix.validate(), false, "validate #7");
});
QUnit.test("matirix row, rowClasses property, isAllRowRequired", function (assert) {
QUnit.test("matrix row, rowClasses property, isAllRowRequired", function (assert) {
var survey = new SurveyModel({
elements: [
{
Expand All @@ -92,7 +92,7 @@ QUnit.test("matirix row, rowClasses property, isAllRowRequired", function (asser
assert.equal(question.visibleRows[0].rowClasses, "row", "first row value is set");
assert.equal(question.visibleRows[1].rowClasses, "row row_error", "Error for the second row");
});
QUnit.test("matirix row, rowClasses property, eachRowUnique", function (assert) {
QUnit.test("matrix row, rowClasses property, eachRowUnique", function (assert) {
const survey = new SurveyModel({
elements: [
{
Expand All @@ -108,20 +108,25 @@ QUnit.test("matirix row, rowClasses property, eachRowUnique", function (assert)
const question = <QuestionMatrixModel>survey.getQuestionByName("q1");
assert.ok(question.cssClasses.row, "Row class is not empty");
assert.equal(question.hasErrorInRow(question.visibleRows[0]), false, "hasErrorInRow(0)");
assert.equal(question.visibleRows[0].hasError, false, "visibleRows[0].hasError");
assert.equal(question.visibleRows[0].rowClasses, "row", "Set row class");
question.value = { row1: "col1", row2: "col1" };
question.validate();
assert.equal(question.hasErrorInRow(question.visibleRows[0]), false, "hasErrorInRow(0) #1");
assert.equal(question.visibleRows[0].hasError, false, "visibleRows[0].hasError #1");
assert.equal(question.visibleRows[0].rowClasses, "row", "first row #1");
assert.equal(question.hasErrorInRow(question.visibleRows[1]), true, "hasErrorInRow(1) #1");
assert.equal(question.visibleRows[1].hasError, true, "visibleRows[1].hasError #1");
assert.equal(question.visibleRows[1].rowClasses, "row row_error", "second row #1");
question.visibleRows[1].value = "col2";
assert.equal(question.hasErrorInRow(question.visibleRows[0]), false, "hasErrorInRow(0) #1");
assert.equal(question.hasErrorInRow(question.visibleRows[0]), false, "hasErrorInRow(0) #2");
assert.equal(question.visibleRows[0].hasError, false, "visibleRows[0].hasError #2");
assert.equal(question.visibleRows[0].rowClasses, "row", "first row #2");
assert.equal(question.hasErrorInRow(question.visibleRows[1]), false, "hasErrorInRow(1) #2");
assert.equal(question.visibleRows[1].hasError, false, "visibleRows[1].hasError #2");
assert.equal(question.visibleRows[1].rowClasses, "row", "second row #2");
});
QUnit.test("matirix row, rowClasses property, #7889", function (assert) {
QUnit.test("matrix row, rowClasses property, #7889", function (assert) {
const survey = new SurveyModel({
elements: [
{
Expand All @@ -137,13 +142,19 @@ QUnit.test("matirix row, rowClasses property, #7889", function (assert) {
const question = <QuestionMatrixModel>survey.getQuestionByName("q1");
assert.ok(question.cssClasses.row, "Row class is not empty");
assert.equal(question.hasErrorInRow(question.visibleRows[0]), false, "hasErrorInRow(0), #1");
assert.equal(question.visibleRows[0].hasError, false, "visibleRows[0].hasError, #1");
assert.equal(question.hasErrorInRow(question.visibleRows[1]), false, "hasErrorInRow(1), #2");
assert.equal(question.visibleRows[1].hasError, false, "visibleRows[1].hasError, #2");
assert.equal(question.hasErrorInRow(question.visibleRows[2]), false, "hasErrorInRow(2), #3");
assert.equal(question.visibleRows[2].hasError, false, "visibleRows[2].hasError, #3");
question.visibleRows[0].cellClick(question.columns[0]);
assert.deepEqual(question.value, { row1: "col1" }, "value is set #4");
assert.equal(question.hasErrorInRow(question.visibleRows[0]), false, "hasErrorInRow(0), #5");
assert.equal(question.visibleRows[0].hasError, false, "visibleRows[0].hasError, #5");
assert.equal(question.hasErrorInRow(question.visibleRows[1]), false, "hasErrorInRow(1), #6");
assert.equal(question.visibleRows[1].hasError, false, "visibleRows[1].hasError, #6");
assert.equal(question.hasErrorInRow(question.visibleRows[2]), false, "hasErrorInRow(2), #7");
assert.equal(question.visibleRows[2].hasError, false, "visibleRows[2].hasError, #7");
});
QUnit.test("check row randomization in design mode", (assert) => {

Expand Down Expand Up @@ -268,7 +279,7 @@ QUnit.test("rows.class, ItemValue.enableIf", (assert) => {
survey.css.matrix.rowReadOnly = prevCssValue;
}
});
QUnit.test("matirix isAllRowRequired & getItemClass #7963", function (assert) {
QUnit.test("matrix isAllRowRequired & getItemClass #7963", function (assert) {
const survey = new SurveyModel({
elements: [
{
Expand All @@ -290,7 +301,7 @@ QUnit.test("matirix isAllRowRequired & getItemClass #7963", function (assert) {
question.value = { row1: "col1" };
assert.equal(question.getItemClass(row, column).indexOf(itemError) > -1, false, "itemError doesn't exist");
});
QUnit.test("matirix isRequired & getItemClass #7963", function (assert) {
QUnit.test("matrix isRequired & getItemClass #7963", function (assert) {
const survey = new SurveyModel({
elements: [
{
Expand All @@ -312,7 +323,7 @@ QUnit.test("matirix isRequired & getItemClass #7963", function (assert) {
question.value = { row2: "col1" };
assert.equal(question.getItemClass(row, column).indexOf(itemError) > -1, false, "itemError doesn't exist");
});
QUnit.test("matirix isAllRowRequired & isRequired & getItemClass #7963", function (assert) {
QUnit.test("matrix isAllRowRequired & isRequired & getItemClass #7963", function (assert) {
const survey = new SurveyModel({
elements: [
{
Expand Down

0 comments on commit b214283

Please sign in to comment.