I'm using SurveyJS to create a multi step form for users to check their existing profile. The survey is pre-populated with values from an API, so the user can check those and submit changes. Therefore, I also want to populate file upload fields, but can't find a way to do that with remote files.
I'm aware, I can load default values with base64 encoded images, like so:
import { Model } from 'survey-core'
import { Survey } from 'survey-react-ui'
import './App.css'
const surveyJson = {
elements: [{
name: "file1",
title: "Upload a file",
allowMultiple: true,
type: "file"
}]
};
const App = () => {
const survey = new Model(surveyJson);
survey.mergeData({
file1: [
{
name: 'file1.svg',
type: 'image/svg+xml',
content: 'data:image/svg+xml;base64,.....'
}
]
})
return (
<div style={{ minWidth: '500px', minHeight: '400px' }}>
<Survey model={survey} />
</div>
)
}
export default App
But this only works once the survey is created and mounted. Since my survey consists of multiple pages and images in them, I would need to download every image and convert it to base64, before loading the survey, like:
useEffect(() => {
fetch('https://api.example.com/person')
.then(response => response.json())
.then(data => {
const person = data.person;
person.files = person.files.map((file: any) => {
// fetch file and convert to base64
const base64 = "....";
return {
name: file.name,
type: file.type,
content: base64
}
});
survey.mergeData(person);
});
}, []);
This seems quite ineffective to me. Maybe there is a more elegant solution to achieve this?
Appreciate you taking the time to nicely describe your question.
You're correct that the current approach is suboptimal. Preloading all images before initializing SurveyJS can indeed cause unnecessary delays and UI thread blocking, especially in a multi-step form scenario and multiple images you need to process for the model.
A more efficient approach would be to defer image loading until after the survey is initialized and to lazy load images as they are needed (about to become visible). This is particularly useful for multi-step forms, where users might take some time to navigate to the next step and your cases is a great fit for this approach.
Fortunately, SurveyJS supports lazy rendering based on the viewport visibility, which can be enabled with the following setting: settings.lazyRender.enabled = true
. This brings additional benefits as the lazy loading render speeds up the time to interactive for your form.
Additionally, SurveyJS provides the onAfterRenderQuestion
event, which fires after each individual question is rendered (documentation link). This event can be leveraged to lazy load images only when the respective file question is actually rendered. By using this event, you can assign the image value to the file question dynamically with the expression options.question.value = imageObj; //imageObj has the structure you have described {type: "...", content: "..."}
.
To demonstrate this approach, I’ve created a solution that you can explore.
Main code is here
Survey.settings.lazyRender.enabled = true;
function updateFileTemplate(_, options){
if (!options.question || options.question.getType() != "file") return; // we only care for file questions
const imageQuestion = options.question; // get the image question
getRandomProfileImage()
.then(profileImage => {
imageQuestion.value = profileImage; // set the value
})
.catch(error => {
console.error(`Error during loading ${imageQuestion.name}. Error is: ${error}`)
});
}
const survey = new Survey.Model(surveyJSON);
survey.onAfterRenderQuestion.add(updateFileTemplate); // attach the updateFileTemplate function as a `onAfterRenderQuestion` event handler
onAfterRenderQuestion
event. I do it to demonstrate that the approach is viablestoreDataAsText
property (
https://surveyjs.io/form-library/documentation/api-reference/file-model#storeDataAsText). As per SurveyJS docs "If you disable this property, implement SurveyModel's onUploadFiles event handler to specify how to store file content.". Essentially, leveraging this, you can implement your own storage logic for the file contents and just store the image as an Url solving a lot of the concerns you had by design. The benefit is that you will also decrease the size of the JSON making submissions faster, not to mention you can lazy load images using more standard approaches like Intersection Observer API. This depends more on the backend you have and is more work, but I think it's great option to keep in mind.Hope this helps!