Skip to content

Commit

Permalink
Matrix: Implement errors in rows (#6635)
Browse files Browse the repository at this point in the history
* Work for #6613: implement errors row model

* Work for #6613: implement in knockout

* Work for #6613: implement in React

* Work for #6613: implement in angular

* Work for #6613: Implement in Vue2

* Work for #6613: Implement in Vue3

* Add markup tests

* Make all themes calculate isMoble flag

* Fix knockout build

* Fix vue3 build

* Work for #6613: implement errors styles

* Fix vrt tests

* Fix d&d

* Fix f tests

* Add unit test on matrixdynamic d&d

* Fix detail panel is not hiding

* Update etalons

* Fix testcafe tests

* Unify space between required text and title in all platforms

* Fix angular vrt tests

* Fix vrt tests in Vue2

* Fix errors are not reactive sometimes in Vue3
  • Loading branch information
dk981234 committed Aug 3, 2023
1 parent 0ec0410 commit 05ed4e0
Show file tree
Hide file tree
Showing 60 changed files with 1,476 additions and 571 deletions.
2 changes: 2 additions & 0 deletions examples_test/default/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
function init() {
//disable observer for survey to prevent loop limited exceptions
Survey.defaultStandardCss.variables.mobileWidth = undefined;
Survey.StylesManager.applyTheme("default");
}
document.addEventListener("DOMContentLoaded", init);
5 changes: 5 additions & 0 deletions examples_test/modern/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function init() {
//disable observer for survey to prevent loop limited exceptions
Survey.modernCss.variables.mobileWidth = undefined;
}
document.addEventListener("DOMContentLoaded", init);
1 change: 1 addition & 0 deletions examples_test/modern/knockout.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<link rel="stylesheet" href="../../build/survey-knockout/modern.min.css"/>
<script src="../../build/survey-knockout/survey.ko.min.js"></script>
<script src="./index.js"></script>
</head>
<body>
<div id="surveyElement"></div>
Expand Down
1 change: 1 addition & 0 deletions examples_test/modern/react.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<script src="https://unpkg.com/react-dom@16.5.0/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="../../build/survey-react/modern.min.css"/>
<script src="../../build/survey-react/survey.react.min.js"></script>
<script src="./index.js"></script>
</head>
<body>
<div id="surveyElement"></div>
Expand Down
1 change: 1 addition & 0 deletions examples_test/modern/vue.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js"></script>
<link rel="stylesheet" href="../../build/survey-vue/modern.min.css"/>
<script src="../../build/survey-vue/survey.vue.min.js"></script>
<script src="./index.js"></script>
</head>
<body>
<div id="surveyElement"></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import "survey-core/survey.i18n";
@Component({
selector: "test",
template: "<router-outlet></router-outlet>",
})
})
export class TestComponent {
public model?: SurveyModel | Survey.PopupSurveyModel;
public isPopup: boolean = true;
constructor(private changeDetectorRef: ChangeDetectorRef) {
(window as any).Survey = Survey;
Survey.defaultStandardCss.variables.mobileWidth = <any>undefined;
Survey.modernCss.variables.mobileWidth = <any>undefined;
(<any>window).setSurvey = (survey: SurveyModel | Survey.PopupSurveyModel, isPopup: boolean) => {
this.model = survey;
this.isPopup = isPopup;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<ng-template #template>
<tr [class]="model.className" (pointerdown)="question.onPointerDown($event, row)" [attr.data-sv-drop-target-matrix-row]="row && row.id">
<sv-ng-matrix-cell
[cell]="cell"
[question]="question"
*ngFor="let cell of model.cells; trackBy: trackCellBy"
></sv-ng-matrix-cell>
<tr *ngIf="model.visible" [class]="model.className" (pointerdown)="question.onPointerDown($event, row)"
[attr.data-sv-drop-target-matrix-row]="row && row.id">
<sv-ng-matrix-cell [cell]="cell" [question]="question"
*ngFor="let cell of model.cells; trackBy: trackCellBy"></sv-ng-matrix-cell>
</tr>
</ng-template>
45 changes: 24 additions & 21 deletions packages/survey-angular-ui/src/questions/matrixcell.component.html
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
<ng-template #template>
<td [class]="cell.className" [attr.data-responsive-title]="getHeaders()" [title]="cell.getTitle()" [style]="getCellStyle()" [attr.colspan]="cell.colSpans" #cellContainer>
<sv-ng-matrix-drag-drop-icon *ngIf="cell.isDragHandlerCell" [model]="$any({ data: { row: row, question: question } })"></sv-ng-matrix-drag-drop-icon>
<td [class]="cell.className" [attr.data-responsive-title]="getHeaders()" [title]="cell.getTitle()"
[style]="getCellStyle()" [attr.colspan]="cell.colSpans" #cellContainer>
<sv-ng-matrix-drag-drop-icon *ngIf="cell.isDragHandlerCell"
[model]="$any({ data: { row: row, question: question } })"></sv-ng-matrix-drag-drop-icon>
<sv-action-bar *ngIf="cell.isActionsCell" [model]="cell.item.getData()" [handleClick]="false"></sv-action-bar>
<ng-container *ngIf="cell.hasPanel">
<ng-template [component]="{ name: panelComponentName, data: panelComponentData }"></ng-template>
</ng-container>
<div *ngIf="cell.hasQuestion" [class]="question.cssClasses.cellQuestionWrapper" [visible]="cell.question.isVisible">
<div *ngIf="cell.showErrorOnTop && cell.question.hasVisibleErrors" [element]="cell.question" [location]="'top'" sv-ng-errors></div>
<div *ngIf="cell.isErrorsCell && cell.question?.hasVisibleErrors" [element]="cell.question" sv-ng-errors></div>
<div *ngIf="cell.hasQuestion" [class]="question.cssClasses.cellQuestionWrapper"
[visible]="cell.question.isVisible">
<ng-container *ngIf="!cell.isChoice && cell.question.isDefaultRendering()">
<ng-template [component]="{ name: question.getCellWrapperComponentName(cell.cell), data: { componentData: question.getCellWrapperComponentData(cell.cell)} }">
<ng-template [component]="{ name: getComponentName(cell.question), data: { model: cell.question } }"></ng-template>
</ng-template>
<ng-template
[component]="{ name: question.getCellWrapperComponentName(cell.cell), data: { componentData: question.getCellWrapperComponentData(cell.cell)} }">
<ng-template
[component]="{ name: getComponentName(cell.question), data: { model: cell.question } }"></ng-template>
</ng-template>
</ng-container>
<ng-template *ngIf="!cell.isChoice && !cell.question.isDefaultRendering()" [component]="{ name: cell.question.getComponentName(), data: { model: cell.question } }">
<ng-template *ngIf="!cell.isChoice && !cell.question.isDefaultRendering()"
[component]="{ name: cell.question.getComponentName(), data: { model: cell.question } }">
</ng-template>
<ng-container *ngIf="cell.isItemChoice">
<ng-template [component]="{ name: question.getCellWrapperComponentName(cell.cell), data: { componentData: question.getCellWrapperComponentData(cell.cell)} }">
<sv-ng-selebase-item
[showLabel]="false"
[inputType]="cell.isCheckbox ? 'checkbox': 'radio'"
[question]="cell.question"
[model]="cell.item"
></sv-ng-selebase-item>
<ng-template
[component]="{ name: question.getCellWrapperComponentName(cell.cell), data: { componentData: question.getCellWrapperComponentData(cell.cell)} }">
<sv-ng-selebase-item [showLabel]="false" [inputType]="cell.isCheckbox ? 'checkbox': 'radio'"
[question]="cell.question" [model]="cell.item"></sv-ng-selebase-item>
</ng-template>
</ng-container>
<div *ngIf="cell.isOtherChoice" [class]="cell.question.getCommentAreaCss(true)" [question]="cell.question" sv-ng-comment-other></div>
<div *ngIf="cell.showErrorOnBottom && cell.question.hasVisibleErrors" [element]="cell.question" [location]="'top'" sv-ng-errors></div>
<div *ngIf="cell.question.isErrorsModeTooltip && cell.question.hasVisibleErrors" [element]="cell.question" [location]="'tooltip'" sv-ng-errors></div>
<div *ngIf="cell.isOtherChoice" [class]="cell.question.getCommentAreaCss(true)" [question]="cell.question"
sv-ng-comment-other></div>
</div>
<ng-container *ngIf="cell.hasTitle">
<ng-template [component]="{ name: question.getCellWrapperComponentName($any(cell)), data: { componentData: question.getCellWrapperComponentData($any(cell))} }">
<ng-template
[component]="{ name: question.getCellWrapperComponentName($any(cell)), data: { componentData: question.getCellWrapperComponentData($any(cell))} }">
<sv-ng-string [model]="cell.locTitle"></sv-ng-string>
<span *ngIf="!!cell.requiredText" [class]="question.cssClasses.cellRequiredText">{{ cell.requiredText }}</span>
<span *ngIf="!!cell.requiredText" [class]="question.cssClasses.cellRequiredText">{{ cell.requiredText }}</span>
</ng-template>
</ng-container>
</td>
</td>
</ng-template>
22 changes: 18 additions & 4 deletions packages/survey-angular-ui/src/questions/matrixcell.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,23 @@ export class MatrixCellComponent extends BaseAngular<Question> {
@Input() cell!: QuestionMatrixDropdownRenderedCell;

@ViewChild("cellContainer") cellContainer!: ElementRef<HTMLElement>;

isVisible: boolean = false;

getModel() {
return this.cell.question;
if(this.cell.hasQuestion) {
return this.cell.question;
}
return null as any;
}
public get row(): MatrixDropdownRowModelBase {
return this.cell.row;
}
public override ngDoCheck(): void {
super.ngDoCheck();
if(this.cell.isErrorsCell && this.cell?.question) {
this.cell.question.registerFunctionOnPropertyValueChanged("errors", () => {
this.update();
}, "__ngSubscription")
}
}
public get panelComponentName(): string {
const panel = this.cell.panel;
const survey = <SurveyModel>panel.survey;
Expand Down Expand Up @@ -75,4 +83,10 @@ export class MatrixCellComponent extends BaseAngular<Question> {
};
this.question.survey.matrixAfterCellRender(this.question, options);
}
override ngOnDestroy(): void {
super.ngOnDestroy();
if(this.cell.isErrorsCell && this.cell?.question) {
this.cell.question.unRegisterFunctionOnPropertyValueChanged("errors", "__ngSubscription")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
@Component({
selector: "sv-ng-matrixheaderrequired",
styles: [":host { display: none; }"],
template: "<ng-template #template><span *ngIf='column.isRenderedRequired' [class]='question.cssClasses.cellRequiredText'>{{ column.requiredText }}</span></ng-template>"
template: "<ng-template #template><ng-container *ngIf='column.isRenderedRequired'><span>&nbsp;</span><span [class]='question.cssClasses.cellRequiredText'>{{ column.requiredText }}</span></ng-container></ng-template>"
})
export class MatrixRequiredHeader extends BaseAngular<MatrixDropdownColumn> {
@Input() column!: MatrixDropdownColumn;
Expand Down
2 changes: 2 additions & 0 deletions packages/survey-vue3-ui/example/src/components/test/Test.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import $ from 'jquery'
import * as showdown from "showdown"
import { defaultCss } from "survey-core/plugins/bootstrap-integration";
Survey.defaultStandardCss.variables.mobileWidth = undefined as any;
Survey.modernCss.variables.mobileWidth = undefined as any;
(window as any).Survey = Object.assign({}, Survey);
(window as any).Survey.defaultBootstrapCss = defaultCss;
window.jQuery = window.$ = $;
Expand Down
16 changes: 1 addition & 15 deletions packages/survey-vue3-ui/src/MatrixCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:colspan="cell.colSpans"
ref="root"
>
<survey-errors v-if="cell.isErrorsCell" :element="cell.question" />
<sv-matrix-drag-drop-icon
v-if="cell.isDragHandlerCell"
:item="{ data: { row: cell.row, question: question } }"
Expand All @@ -26,11 +27,6 @@
v-if="cell.hasQuestion"
:class="question.cssClasses.cellQuestionWrapper"
>
<survey-errors
v-if="cell.showErrorOnTop"
:element="cell.question"
:location="'top'"
/>
<component
v-if="!cell.isChoice && cell.question.isDefaultRendering()"
v-show="isVisible"
Expand Down Expand Up @@ -65,16 +61,6 @@
v-if="cell.isOtherChoice"
:question="cell.question"
/>
<survey-errors
v-if="cell.showErrorOnBottom"
:element="cell.question"
:location="'bottom'"
/>
<survey-errors
v-if="cell.question.isErrorsModeTooltip"
:element="cell.question"
:location="'tooltip'"
/>
</div>
<survey-string v-if="cell.hasTitle" :locString="cell.locTitle" />
<span
Expand Down
13 changes: 8 additions & 5 deletions packages/survey-vue3-ui/src/MatrixHeaderRequired.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<template>
<span
v-if="column.isRenderedRequired"
:class="question.cssClasses.cellRequiredText"
>{{ column.requiredText }}</span
>
<template v-if="column.isRenderedRequired">
<span>&nbsp;</span>
<span
v-if="column.isRenderedRequired"
:class="question.cssClasses.cellRequiredText"
>{{ column.requiredText }}</span
>
</template>
</template>

<script lang="ts" setup>
Expand Down
24 changes: 14 additions & 10 deletions packages/survey-vue3-ui/src/MatrixTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,23 @@
</tr>
</thead>
<tbody>
<tr
<template
v-for="row in table.rows"
:data-sv-drop-target-matrix-row="row.row && row.row.id"
:class="row.className"
:key="question.inputId + '_' + row.id"
>
<survey-matrixcell
:cell="cell"
:question="question"
v-for="(cell, cellIndex) in row.cells"
:key="row.id + '_' + cellIndex"
/>
</tr>
<tr
:data-sv-drop-target-matrix-row="row.row && row.row.id"
:class="row.className"
v-if="row.visible"
>
<survey-matrixcell
:cell="cell"
:question="question"
v-for="(cell, cellIndex) in row.cells"
:key="row.id + '_' + cellIndex"
/>
</tr>
</template>
</tbody>
<tfoot v-if="table.showFooter">
<tr>
Expand Down
20 changes: 12 additions & 8 deletions packages/survey-vue3-ui/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Base.createPropertiesHash = () => {
return res;
};
function makeReactive(surveyElement: Base) {
if (!surveyElement) return;
if (!surveyElement || (surveyElement as any).__vueImplemented) return false;
surveyElement.createArrayCoreHandler = (hash, key: string): Array<any> => {
const arrayRef = shallowRef(hash[key]);
hash[key]["onArrayChanged"] = () => {
Expand Down Expand Up @@ -46,16 +46,19 @@ function makeReactive(surveyElement: Base) {
hash[key] = val;
}
};
(surveyElement as any).__vueImplemented = true;
return true;
}

function unMakeReactive(surveyElement: Base) {
if (!surveyElement) return;
function unMakeReactive(surveyElement: Base, isModelSubsribed: boolean) {
if (!surveyElement || !isModelSubsribed) return;
surveyElement.iteratePropertiesHash((hash, key) => {
hash[key] = unref(hash[key]);
if (Array.isArray(hash[key])) {
hash[key]["onArrayChanged"] = undefined;
}
});
delete (surveyElement as any).__vueImplemented;
surveyElement.createArrayCoreHandler = undefined as any;
surveyElement.getPropertyValueCoreHandler = undefined as any;
surveyElement.setPropertyValueCoreHandler = undefined as any;
Expand All @@ -67,26 +70,27 @@ export function useBase<T extends Base>(
onModelChanged?: (newValue: T) => void,
clean?: (model: T) => void
) {
let isModelSubsribed = false;
const stopWatch = watch(
getModel,
(value, oldValue) => {
if (onModelChanged) onModelChanged(value);
if (oldValue) {
unMakeReactive(oldValue);
unMakeReactive(oldValue, isModelSubsribed);
if (clean) clean(oldValue);
}

makeReactive(value);
isModelSubsribed = makeReactive(value);
},
{
immediate: true,
}
);
onUnmounted(() => {
onBeforeUnmount(() => {
const model = getModel();
if (model) {
if (clean) clean(getModel());
unMakeReactive(model);
unMakeReactive(model, isModelSubsribed);
stopWatch();
}
});
Expand Down Expand Up @@ -127,7 +131,7 @@ export function useLocString(
},
{ immediate: true }
);
onUnmounted(() => {
onBeforeUnmount(() => {
stopWatch();
});
return renderedHtml;
Expand Down
1 change: 1 addition & 0 deletions src/defaultCss/cssmodern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ export var modernCss = {
},
},
variables: {
mobileWidth: "--sv-mobile-width",
themeMark: "--sv-modern-mark"
}
};
Expand Down
1 change: 1 addition & 0 deletions src/defaultCss/cssstandard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ export var defaultStandardCss = {
},
},
variables: {
mobileWidth: "--sv-mobile-width",
themeMark: "--sv-default-mark"
},
tagbox: {
Expand Down
6 changes: 6 additions & 0 deletions src/defaultCss/defaultV2Css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ export var defaultV2Css = {
tableWrapper: "sd-table-wrapper",
rootAlternateRows: "sd-table--alternate-rows",
cell: "sd-table__cell",
errorsCell: "sd-table__cell--error",
errorsCellTop: "sd-table__cell--error-top",
errorsCellBottom: "sd-table__cell--error-bottom",
itemCell: "sd-table__cell--item",
row: "sd-table__row",
headerCell: "sd-table__cell sd-table__cell--header",
Expand Down Expand Up @@ -461,6 +464,9 @@ export var defaultV2Css = {
emptyCell: "sd-table__cell--empty",
verticalCell: "sd-table__cell--vertical",
cellQuestionWrapper: "sd-table__question-wrapper",
errorsCell: "sd-table__cell--error",
errorsCellTop: "sd-table__cell--error-top",
errorsCellBottom: "sd-table__cell--error-bottom",
compact: "sd-element--with-frame sd-element--compact"
},
rating: {
Expand Down

0 comments on commit 05ed4e0

Please sign in to comment.