Skip to content

Commit b03f939

Browse files
committed
feat(json-editor): Enable immediate save without tab switching
- Enhanced text getter to retrieve from JSON plugin when active - Added syncJsonEditorText() method for explicit synchronization - Set isJSONChanged flag immediately on text changes - Maintains full backward compatibility Fixes #7212 Signed-off-by: Giovanni Magliocchetti <[email protected]>
1 parent bdba7e9 commit b03f939

File tree

2 files changed

+1740
-588
lines changed

2 files changed

+1740
-588
lines changed

packages/survey-creator-core/src/components/tabs/json-editor-plugin.ts

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { Base, property, ListModel, Action, ComputedUpdater } from "survey-core";
1+
import {
2+
Base,
3+
property,
4+
ListModel,
5+
Action,
6+
ComputedUpdater,
7+
} from "survey-core";
28
import { SurveyCreatorModel } from "../../creator-base";
39
import { ICreatorPlugin } from "../../creator-settings";
410
import { SurveyTextWorker, SurveyTextWorkerError } from "../../textWorker";
@@ -24,13 +30,16 @@ export abstract class JsonEditorBaseModel extends Base {
2430
}
2531
protected abstract getText(): string;
2632
protected abstract setText(val: string): void;
27-
protected onEditorActivated(): void { }
33+
protected onEditorActivated(): void {}
2834
public onPluginActivate(): void {
2935
this.text = this.creator.text;
3036
this.onEditorActivated();
3137
this.isJSONChanged = false;
3238
}
3339
protected onTextChanged(): void {
40+
// Mark that JSON has changed
41+
this.isJSONChanged = true;
42+
3443
if (this.jsonEditorChangedTimeoutId !== -1) {
3544
clearTimeout(this.jsonEditorChangedTimeoutId);
3645
}
@@ -57,12 +66,12 @@ export abstract class JsonEditorBaseModel extends Base {
5766
if (!!error)this.gotoError(error.at, error.rowAt, error.columnAt);
5867
},
5968
allowSelection: false,
60-
searchEnabled: false
69+
searchEnabled: false,
6170
});
6271
this.errorListValue.cssClasses = {
6372
item: "svc-json-errors__item",
6473
itemBody: "svc-json-error",
65-
itemsContainer: "svc-json-errors"
74+
itemsContainer: "svc-json-errors",
6675
};
6776
this.errorListValue.hasVerticalScroller = true;
6877
}
@@ -73,41 +82,45 @@ export abstract class JsonEditorBaseModel extends Base {
7382
let hasErrors = errors.length > 0;
7483
if (hasErrors) {
7584
const actions = [];
76-
this.createErrorActions(errors).forEach(action => actions.push(action));
85+
this.createErrorActions(errors).forEach((action) => actions.push(action));
7786
this.errorList.setItems(actions);
7887
}
7988
this.hasErrors = hasErrors;
8089
}
81-
protected gotoError(at: number, row: number, column: number): void { }
82-
private createErrorActions(errors: Array<SurveyTextWorkerError>): Array<Action> {
90+
protected gotoError(at: number, row: number, column: number): void {}
91+
private createErrorActions(
92+
errors: Array<SurveyTextWorkerError>,
93+
): Array<Action> {
8394
const res = [];
8495
let counter = 1;
85-
errors.forEach(error => {
96+
errors.forEach((error) => {
8697
const line = error.rowAt > -1 ? "Line: " + (error.rowAt + 1) + ". " : "";
8798
let title = error.text;
8899
if (title.length > maxErrorLength + 3) {
89100
title = title.substring(0, maxErrorLength) + "...";
90101
}
91102
title = line + title;
92103
const at = error.at;
93-
res.push(new Action({
94-
id: "error_" + counter++,
95-
component: "json-error-item",
96-
title: title,
97-
tooltip: error.text,
98-
iconName: "icon-error",
99-
iconSize: "auto",
100-
data: {
101-
error: error,
102-
showFixButton: error.isFixable,
103-
fixError: () => {
104-
this.text = error.fixError(this.text);
104+
res.push(
105+
new Action({
106+
id: "error_" + counter++,
107+
component: "json-error-item",
108+
title: title,
109+
tooltip: error.text,
110+
iconName: "icon-error",
111+
iconSize: "auto",
112+
data: {
113+
error: error,
114+
showFixButton: error.isFixable,
115+
fixError: () => {
116+
this.text = error.fixError(this.text);
117+
},
118+
fixButtonIcon: "icon-fix",
119+
//todo
120+
fixButtonTitle: "Fix error",
105121
},
106-
fixButtonIcon: "icon-fix",
107-
//todo
108-
fixButtonTitle: "Fix error"
109-
}
110-
}));
122+
}),
123+
);
111124
});
112125
return res;
113126
}
@@ -129,14 +142,18 @@ export abstract class TabJsonEditorBasePlugin implements ICreatorPlugin {
129142
public static iconName = "icon-codeeditor-24x24";
130143

131144
constructor(private creator: SurveyCreatorModel) {
132-
this.createActions().forEach(action => creator.toolbar.actions.push(action));
145+
this.createActions().forEach((action) =>
146+
creator.toolbar.actions.push(action),
147+
);
133148
}
134149

135150
public saveToFileHandler = saveToFileHandler;
136151

137152
public exportToFile(fileName: string) {
138153
if (this.model) {
139-
const jsonBlob = new Blob([this.model.text], { type: "application/json" });
154+
const jsonBlob = new Blob([this.model.text], {
155+
type: "application/json",
156+
});
140157
this.saveToFileHandler(fileName, jsonBlob);
141158
}
142159
}
@@ -167,7 +184,9 @@ export abstract class TabJsonEditorBasePlugin implements ICreatorPlugin {
167184
iconSize: "auto",
168185
locTitleName: "ed.surveyJsonImportButton",
169186
locTooltipName: "ed.surveyJsonImportButton",
170-
visible: <any>new ComputedUpdater<boolean>(() => { return this.creator.activeTab === "json"; }),
187+
visible: <any>new ComputedUpdater<boolean>(() => {
188+
return this.creator.activeTab === "json";
189+
}),
171190
mode: "small",
172191
component: "sv-action-bar-item",
173192
needSeparator: true,
@@ -184,7 +203,7 @@ export abstract class TabJsonEditorBasePlugin implements ICreatorPlugin {
184203
};
185204
}
186205
this.inputFileElement.click();
187-
}
206+
},
188207
});
189208
items.push(this.importAction);
190209

@@ -194,12 +213,14 @@ export abstract class TabJsonEditorBasePlugin implements ICreatorPlugin {
194213
iconSize: "auto",
195214
locTitleName: "ed.surveyJsonExportButton",
196215
locTooltipName: "ed.surveyJsonExportButton",
197-
visible: <any>new ComputedUpdater<boolean>(() => { return this.creator.activeTab === "json"; }),
216+
visible: <any>new ComputedUpdater<boolean>(() => {
217+
return this.creator.activeTab === "json";
218+
}),
198219
mode: "small",
199220
component: "sv-action-bar-item",
200221
action: () => {
201222
this.exportToFile(settings.jsonEditor.exportFileName);
202-
}
223+
},
203224
});
204225
items.push(this.exportAction);
205226

@@ -209,12 +230,14 @@ export abstract class TabJsonEditorBasePlugin implements ICreatorPlugin {
209230
iconSize: "auto",
210231
locTitleName: "ed.surveyJsonCopyButton",
211232
locTooltipName: "ed.surveyJsonCopyButton",
212-
visible: <any>new ComputedUpdater<boolean>(() => { return this.creator.activeTab === "json"; }),
233+
visible: <any>new ComputedUpdater<boolean>(() => {
234+
return this.creator.activeTab === "json";
235+
}),
213236
mode: "small",
214237
component: "sv-action-bar-item",
215238
action: () => {
216239
this.copyToClipboard();
217-
}
240+
},
218241
});
219242
items.push(this.copyAction);
220243

@@ -245,6 +268,6 @@ export abstract class TabJsonEditorBasePlugin implements ICreatorPlugin {
245268
return !textWorker.isJsonHasErrors;
246269
}
247270
protected abstract createModel(
248-
creator: SurveyCreatorModel
271+
creator: SurveyCreatorModel,
249272
): JsonEditorBaseModel;
250273
}

0 commit comments

Comments
 (0)