Source on GitHub
Definition: shared/definitions/resume-progress.json
shared/client/resume-progress.js · sjs:resume-progress.client
View on GitHub
import debounce from "lodash/debounce";
// On return: restore both — the user continues on the same page, in the same input.
// Restore before listening, so restoring the page doesn't count as a change to save.
const saved = await fetch(`/api/progress/${formId}`).then(r => (r.ok ? r.json() : null));
if (saved) {
survey.data = saved.data;
survey.uiState = saved.uiState;
}
// Save answers + UI state as the user works (uiState: see the Form Library API reference)
const saveProgress = debounce(() => {
fetch(`/api/progress/${formId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data: survey.data, uiState: survey.uiState }),
keepalive: true // lets the last request finish while the tab closes
});
}, 800);
survey.onValueChanged.add(saveProgress);
survey.onCurrentPageChanged.add(saveProgress);
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") saveProgress.flush();
});
routes/examples/resume-progress.php · sjs:resume-progress.server
View on GitHub
// GET /api/progress/{formId} — return the stored progress (answers + UI state) (404 if none yet)
Route::get('/api/progress/{formId}', function (string $formId) {
$json = DB::table('progress')->where('key', $formId)->value('json') ?? abort(404, 'No progress saved yet');
return response($json)->header('Content-Type', 'application/json');
});
// PUT /api/progress/{formId} — store the progress (answers + UI state) as-is: it's one JSON document
// Real code keys it by user and form, e.g. Auth::id().':'.$formId; the samples key by form for brevity
Route::put('/api/progress/{formId}', function (Request $request, string $formId) {
// The raw body, not $request->input(): PHP arrays would turn {} into [] in the stored JSON
$progress = json_decode($request->getContent(), flags: JSON_THROW_ON_ERROR);
StoredJson::put('progress', $formId, $progress); // stored as it arrived: 1.0 stays 1.0
return response()->noContent();
});