Skip to content

Commit bb81c70

Browse files
authored
Merge pull request #37 from surveyjs/local-first-form-builder
Local first form builder
2 parents 898c20b + 39a33e1 commit bb81c70

File tree

2 files changed

+119
-38
lines changed

2 files changed

+119
-38
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SurveyJS Form Builder – Offline Preact Demo
2+
3+
This is a lightweight demo showcasing how to use the [SurveyJS Form Builder](https://surveyjs.io/open-source) in an **offline-first Preact** setup.
4+
5+
## Features
6+
7+
- How to self-host the SurveyJS Form Builder
8+
- How to work completely offline (no internet required)
9+
- How to save/load survey JSON and theme to/from `localStorage`
10+
- How to sync survey data manually when needed
11+
12+
## Related Blog Post
13+
Read the full tutorial here: [Build Truly Offline Web Forms with SurveyJS](https://surveyjs.io/stay-updated/blog/local-fist-form-builder#try-the-demo-offline-surveyjs-creator).
14+
15+
## Tech Stack
16+
17+
- Preact
18+
- SurveyJS Form Library
19+
- LocalStorage API
20+
21+
## How to Run
22+
23+
1. Clone this repository
24+
2. Open `index.html` directly in a browser — no server required
25+
26+
This demo is static and runs 100% in the browser.

blogpost-apps/local-first-form-builder/index.html

Lines changed: 93 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
6-
<title>SurveyJS Creator V2 (pReact version)</title>
6+
<title>SurveyJS Form Builder (pReact version)</title>
77

88
<script src="./survey.core.js"></script>
99
<script src="./survey.i18n.js"></script>
@@ -21,52 +21,107 @@
2121
<body>
2222
<div id="surveyCreatorContainer" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0;"></div>
2323

24-
<script>
25-
const LOCAL_STORAGE_KEY = "localSurveyJSON";
26-
const SYNCED_FLAG_KEY = "localSurveySynced";
24+
<script>
25+
const LOCAL_STORAGE_KEY = "localSurveyJSON";
26+
const THEME_STORAGE_KEY = "localSurveyTheme";
27+
const SYNCED_FLAG_KEY = "localSurveySynced";
2728

28-
SurveyCreatorCore.registerCreatorTheme(SurveyCreatorTheme);
29+
let isSurveySaved = false;
30+
let isThemeSaved = false;
2931

30-
const creator = new SurveyCreator.SurveyCreator();
32+
SurveyCreatorCore.registerCreatorTheme(SurveyCreatorTheme);
33+
SurveyCreatorCore.registerSurveyTheme(SurveyTheme);
3134

32-
creator.autoSaveEnabled = true;
35+
const creator = new SurveyCreator.SurveyCreator({
36+
showThemeTab: true,
37+
showTranslationTab: true
38+
});
3339

34-
const savedJSON = localStorage.getItem(LOCAL_STORAGE_KEY);
35-
if (savedJSON) {
36-
try {
37-
creator.JSON = JSON.parse(savedJSON);
38-
} catch (e) {
39-
console.warn("Failed to parse saved JSON from localStorage:", e);
40-
}
40+
creator.autoSaveEnabled = true;
41+
42+
// Restore saved JSON
43+
const savedJSON = localStorage.getItem(LOCAL_STORAGE_KEY);
44+
if (savedJSON) {
45+
try {
46+
creator.JSON = JSON.parse(savedJSON);
47+
} catch (e) {
48+
console.warn("Failed to parse saved JSON:", e);
49+
}
50+
}
51+
52+
// Restore theme
53+
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
54+
if (savedTheme) {
55+
try {
56+
creator.theme = JSON.parse(savedTheme);
57+
} catch (e) {
58+
console.warn("Failed to parse saved theme:", e);
59+
}
60+
}
61+
62+
63+
function markSyncedIfBothSaved() {
64+
if (isSurveySaved && isThemeSaved) {
65+
localStorage.setItem(SYNCED_FLAG_KEY, "true");
66+
console.log("Both survey and theme saved. Synced flag set.");
67+
} else {
68+
localStorage.setItem(SYNCED_FLAG_KEY, "false");
4169
}
70+
}
4271

43-
creator.saveSurveyFunc = (saveNo, callback) => {
72+
// Persist a survey JSON
73+
creator.saveSurveyFunc = (saveNo, callback) => {
74+
try {
4475
const currentJSON = creator.JSON;
4576
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(currentJSON));
46-
localStorage.setItem(SYNCED_FLAG_KEY, "false");
47-
console.log("Survey auto-saved locally.");
48-
callback(saveNo, true);
49-
};
50-
51-
window.addEventListener("online", () => {
52-
const json = localStorage.getItem(LOCAL_STORAGE_KEY);
53-
const isSynced = localStorage.getItem(SYNCED_FLAG_KEY);
54-
55-
if (json && isSynced !== "true") {
56-
sendToServer(JSON.parse(json));
57-
}
58-
});
59-
60-
function sendToServer(data) {
61-
console.log("Syncing with server...", data);
62-
63-
setTimeout(() => {
64-
console.log("Sync complete.");
65-
localStorage.setItem(SYNCED_FLAG_KEY, "true");
66-
}, 1000);
77+
isSurveySaved = true;
78+
console.log("Survey saved.");
79+
} catch (e) {
80+
console.error("Failed to save survey JSON:", e);
81+
isSurveySaved = false;
6782
}
83+
markSyncedIfBothSaved();
84+
callback(saveNo, true);
85+
};
86+
87+
// Persist a survey theme
88+
creator.saveThemeFunc = (saveNo, callback) => {
89+
try {
90+
const currentTheme = creator.theme;
91+
localStorage.setItem(THEME_STORAGE_KEY, JSON.stringify(currentTheme));
92+
isThemeSaved = true;
93+
console.log("Theme saved.");
94+
} catch (e) {
95+
console.error("Failed to save theme:", e);
96+
isThemeSaved = false;
97+
}
98+
markSyncedIfBothSaved();
99+
callback(saveNo, true);
100+
};
101+
102+
window.addEventListener("online", () => {
103+
const isSynced = localStorage.getItem(SYNCED_FLAG_KEY);
104+
const json = localStorage.getItem(LOCAL_STORAGE_KEY);
105+
const theme = localStorage.getItem(THEME_STORAGE_KEY);
106+
107+
if (isSynced !== "true" && json && theme) {
108+
console.log("Syncing with server...");
109+
sendToServer(JSON.parse(json), theme);
110+
}
111+
});
112+
113+
function sendToServer(surveyData, theme) {
114+
// Simulated async sync
115+
setTimeout(() => {
116+
console.log("Synced survey:", surveyData);
117+
console.log("Synced theme:", theme);
118+
localStorage.setItem(SYNCED_FLAG_KEY, "true");
119+
}, 1000);
120+
}
121+
122+
creator.render("surveyCreatorContainer");
123+
</script>
124+
68125

69-
creator.render("surveyCreatorContainer");
70-
</script>
71126
</body>
72127
</html>

0 commit comments

Comments
 (0)