Skip to content
Snippets Groups Projects
Commit 2a4df433 authored by Remco Tukker's avatar Remco Tukker
Browse files

clean up the surveycomponent and fix warning about editing state during render

parent bd5aa6a7
No related branches found
No related tags found
1 merge request!64useBlock in the survey frontend to ask users for confirmation
import React from "react"; import React, { useCallback } from "react";
import { Question, FunctionFactory } from "survey-core"; import { Question, FunctionFactory } from "survey-core";
import { Survey } from "survey-react-ui"; import { Survey } from "survey-react-ui";
import { VerificationStatus } from './Schema'; import { VerificationStatus } from './Schema';
function SurveyComponent({ surveyModel, verificationStatus }) {
function validateWebsiteUrl(params) { function customDescriptionCallback(_, options) {
const value = params[0]; // get the customDescription for matrix rows and set it in the title
if (value === undefined || value == null || value == '') { // attribute so that it shows up as a hover popup
return true; if (options.column['indexValue'] == 0 && 'item' in options.row) {
} const item = options.row['item'] as object;
try { if (item['customDescription'] !== undefined) {
const url = new URL(value); options.htmlElement.parentElement?.children[0].setAttribute("title", item['customDescription']);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (err) {
return false;
} }
} }
}
surveyModel.css = { function validateWebsiteUrl(params) {
question: { const value = params[0];
title: "sv-title sv-question__title sv-header-flex", if (value === undefined || value == null || value == '') {
titleOnError: "sv-question__title--error sv-error-color-fix" return true;
} }
}; try {
const url = new URL(value);
function setVerifyButton(question: Question, state: VerificationStatus) { return url.protocol === 'http:' || url.protocol === 'https:';
} catch (err) {
verificationStatus.current.set(question.name, state); return false;
}
const btn = document.createElement("button"); }
btn.type = "button";
btn.className = "sv-action-bar-item verification"; function hideCheckboxLabels(_, options) {
btn.innerHTML = state; if (options.question.hideCheckboxLabels) {
const classes = options.cssClasses;
if (state == VerificationStatus.Unverified) { classes.root += " hidden-checkbox-labels";
btn.innerHTML = "No change from previous year"; }
btn.className += " verification-required"; }
btn.onclick = function () {
if (surveyModel.mode == "display") { function setVerifyButton(question: Question, state: VerificationStatus, surveyModel) {
return;
} surveyModel.verificationStatus.set(question.name, state);
question.validate();
setVerifyButton(question, VerificationStatus.Verified); const btn = document.createElement("button");
btn.type = "button";
btn.className = "sv-action-bar-item verification";
btn.innerHTML = state;
if (state == VerificationStatus.Unverified) {
btn.innerHTML = "No change from previous year";
btn.className += " verification-required";
btn.onclick = function () {
if (surveyModel.mode == "display") {
return;
} }
} else { question.validate();
btn.innerHTML = "Answer updated" setVerifyButton(question, VerificationStatus.Verified, surveyModel);
btn.className += " verification-ok";
} }
} else {
btn.innerHTML = "Answer updated"
btn.className += " verification-ok";
}
const selector = '[data-name="' + question.name + '"]'; const selector = '[data-name="' + question.name + '"]';
const header = document.querySelector(selector)?.querySelector('h5'); const header = document.querySelector(selector)?.querySelector('h5');
const old = header?.querySelector(".verification"); const old = header?.querySelector(".verification");
if (old) { if (old) {
old.replaceWith(btn); old.replaceWith(btn);
} else { } else {
header?.appendChild(btn); header?.appendChild(btn);
}
} }
}
FunctionFactory.Instance.register("validateWebsiteUrl", validateWebsiteUrl);
surveyModel.onAfterRenderQuestion.add(function (survey, options) { function SurveyComponent({ surveyModel }) {
const status = verificationStatus.current.get(options.question.name);
const alwaysSetVerify = useCallback((_, options) => {
const status = surveyModel.verificationStatus.get(options.question.name);
if (status) { if (status) {
setVerifyButton(options.question, status); setVerifyButton(options.question, status, surveyModel);
} }
}); }, [surveyModel])
surveyModel.onValueChanged.add(function (survey, options) { const updateFromUnverified = useCallback((_, options) => {
const currentStatus = verificationStatus.current.get(options.question.name); const currentStatus = surveyModel.verificationStatus.get(options.question.name);
if (currentStatus == VerificationStatus.Unverified) { if (currentStatus == VerificationStatus.Unverified) {
setVerifyButton(options.question, VerificationStatus.Edited); setVerifyButton(options.question, VerificationStatus.Edited, surveyModel);
} }
}); }, [surveyModel])
surveyModel.onUpdateQuestionCssClasses.add(function (_, options) { if (!FunctionFactory.Instance.hasFunction("validateWebsiteUrl")) {
if (options.question.hideCheckboxLabels) { FunctionFactory.Instance.register("validateWebsiteUrl", validateWebsiteUrl);
const classes = options.cssClasses; }
classes.root += " hidden-checkbox-labels";
} if (!surveyModel.css.question.title.includes("sv-header-flex")) {
}); surveyModel.css.question.title = "sv-title sv-question__title sv-header-flex";
surveyModel.css.question.titleOnError = "sv-question__title--error sv-error-color-fix";
}
if (!surveyModel.onAfterRenderQuestion.hasFunc(alwaysSetVerify)) {
surveyModel.onAfterRenderQuestion.add(alwaysSetVerify);
}
surveyModel.onMatrixAfterCellRender.add((survey, options) => { if (!surveyModel.onValueChanged.hasFunc(updateFromUnverified)) {
// get the customDescription for matrix rows and set it in the title surveyModel.onValueChanged.add(updateFromUnverified);
// attribute so that it shows up as a hover popup }
if (!surveyModel.onUpdateQuestionCssClasses.hasFunc(hideCheckboxLabels)) {
surveyModel.onUpdateQuestionCssClasses.add(hideCheckboxLabels);
}
if (!surveyModel.onMatrixAfterCellRender.hasFunc(customDescriptionCallback)) {
// NB I would have preferred using onAfterRenderQuestion, but unfortunately that is // NB I would have preferred using onAfterRenderQuestion, but unfortunately that is
// not always triggered on re-renders (specifically when extra column become visble or invisible) // not always triggered on re-renders (specifically when extra column become visble or invisible)
surveyModel.onMatrixAfterCellRender.add(customDescriptionCallback);
if (options.column['indexValue'] == 0 && 'item' in options.row) { }
const item = options.row['item'] as object;
if (item['customDescription'] !== undefined) {
options.htmlElement.parentElement?.children[0].setAttribute("title", item['customDescription']);
}
}
});
return <Survey model={surveyModel} /> return <Survey model={surveyModel} />
} }
......
import React, { useEffect, useRef, useState, useCallback } from "react"; import React, { useEffect, useState, useCallback } from "react";
import { Container } from "react-bootstrap"; import { Container } from "react-bootstrap";
import toast, { Toaster } from "react-hot-toast"; import toast, { Toaster } from "react-hot-toast";
import { Model, Serializer } from "survey-core"; import { Model, Serializer } from "survey-core";
...@@ -15,14 +15,9 @@ Serializer.addProperty("question", "hideCheckboxLabels:boolean"); ...@@ -15,14 +15,9 @@ Serializer.addProperty("question", "hideCheckboxLabels:boolean");
function SurveyContainerComponent({ loadFrom }) { function SurveyContainerComponent({ loadFrom }) {
// NB This component should only rerender after the model is retrieved. const [surveyModel, setSurveyModel] = useState<Model>(); // note that this is never updated and we abuse that fact by adding extra state to the surveyModel
// If you change this to rerender more often, stuff will probably break. const { year, nren } = useParams(); // nren stays empty for inspect and try
const [surveyModel, setSurveyModel] = useState<Model>();
const verificationStatus = useRef<Map<string, VerificationStatus>>(new Map());
const { year, nren } = useParams(); // year is always set, nren stays empty for inspect and try
const [error, setError] = useState<string>('loading survey...'); const [error, setError] = useState<string>('loading survey...');
const lockUUID = useRef<string>();
const beforeUnloadListener = useCallback((event) => { const beforeUnloadListener = useCallback((event) => {
event.preventDefault(); event.preventDefault();
...@@ -53,17 +48,17 @@ function SurveyContainerComponent({ loadFrom }) { ...@@ -53,17 +48,17 @@ function SurveyContainerComponent({ loadFrom }) {
} }
} }
for (const questionName in json["verification_status"]) {
verificationStatus.current.set(questionName, json["verification_status"][questionName]);
}
const survey = new Model(json['model']); const survey = new Model(json['model']);
survey.setVariable('surveyyear', year); survey.setVariable('surveyyear', year);
survey.setVariable('previousyear', parseInt(year!) - 1); survey.setVariable('previousyear', parseInt(year!) - 1);
survey.showNavigationButtons = false; survey.showNavigationButtons = false;
survey.verificationStatus = new Map<string, VerificationStatus>();
for (const questionName in json["verification_status"]) {
survey.verificationStatus.set(questionName, json["verification_status"][questionName]);
}
survey.data = json['data']; survey.data = json['data'];
survey.clearIncorrectValues(true); // TODO test if this really removes all old values and such survey.clearIncorrectValues(true); // TODO test if this really removes all old values and such
survey.currentPageNo = json['page']; survey.currentPageNo = json['page'];
...@@ -89,11 +84,11 @@ function SurveyContainerComponent({ loadFrom }) { ...@@ -89,11 +84,11 @@ function SurveyContainerComponent({ loadFrom }) {
} }
const saveData = { const saveData = {
lock_uuid: lockUUID.current, lock_uuid: survey.lockUUID,
new_state: newState, new_state: newState,
data: survey.data, data: survey.data,
page: survey.currentPageNo, page: survey.currentPageNo,
verification_status: Object.fromEntries(verificationStatus.current) verification_status: Object.fromEntries(survey.verificationStatus)
}; };
try { try {
...@@ -116,7 +111,7 @@ function SurveyContainerComponent({ loadFrom }) { ...@@ -116,7 +111,7 @@ function SurveyContainerComponent({ loadFrom }) {
const validateWithAnswerVerification = (validatorFunction) => { const validateWithAnswerVerification = (validatorFunction) => {
let firstValidationError = ''; let firstValidationError = '';
const verificationValidator = (survey, options) => { const verificationValidator = (survey, options) => {
const status = verificationStatus.current.get(options.name); const status = survey.verificationStatus.get(options.name);
if (status == VerificationStatus.Unverified) { if (status == VerificationStatus.Unverified) {
if (firstValidationError == '') { if (firstValidationError == '') {
firstValidationError = options.name; firstValidationError = options.name;
...@@ -180,8 +175,8 @@ function SurveyContainerComponent({ loadFrom }) { ...@@ -180,8 +175,8 @@ function SurveyContainerComponent({ loadFrom }) {
surveyModel.clearIncorrectValues(true); surveyModel.clearIncorrectValues(true);
surveyModel.mode = json['mode']; surveyModel.mode = json['mode'];
surveyModel.lockedBy = json['locked_by'] surveyModel.lockedBy = json['locked_by']
surveyModel.lockUUID = json['lock_uuid'];
surveyModel.status = json['status']; surveyModel.status = json['status'];
lockUUID.current = json['lock_uuid'];
}, },
'releaseLock': async () => { 'releaseLock': async () => {
const response = await fetch('/api/response/unlock/' + year + '/' + nren, { method: 'POST' }); const response = await fetch('/api/response/unlock/' + year + '/' + nren, { method: 'POST' });
...@@ -207,7 +202,7 @@ function SurveyContainerComponent({ loadFrom }) { ...@@ -207,7 +202,7 @@ function SurveyContainerComponent({ loadFrom }) {
<Toaster /> <Toaster />
<Prompt message="Are you sure you want to leave this page? Information you've entered may not be saved." when={() => { return surveyModel.mode == 'edit' && !!nren; }} onPageExit={onPageExitThroughRouter} /> <Prompt message="Are you sure you want to leave this page? Information you've entered may not be saved." when={() => { return surveyModel.mode == 'edit' && !!nren; }} onPageExit={onPageExitThroughRouter} />
<SurveyNavigationComponent surveyModel={surveyModel} surveyActions={surveyActions} year={year} nren={nren}> <SurveyNavigationComponent surveyModel={surveyModel} surveyActions={surveyActions} year={year} nren={nren}>
<SurveyComponent surveyModel={surveyModel} verificationStatus={verificationStatus} /> <SurveyComponent surveyModel={surveyModel} />
</SurveyNavigationComponent> </SurveyNavigationComponent>
</Container> </Container>
); );
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment