Skip to content

Commit 6c1c013

Browse files
authored
✨ Form playground (ITISFoundation#2865)
* added form playground
1 parent 1c95d00 commit 6c1c013

File tree

5 files changed

+191
-30
lines changed

5 files changed

+191
-30
lines changed

services/web/client/source/class/osparc/Application.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ qx.Class.define("osparc.Application", {
239239
}
240240
break;
241241
}
242+
case "form-sandbox": {
243+
this.__loadView(new osparc.desktop.FormSandboxPage(), {}, false);
244+
}
242245
}
243246
},
244247

@@ -351,7 +354,7 @@ qx.Class.define("osparc.Application", {
351354
this.__loadView(new osparc.viewer.MainPage(studyId, viewerNodeId));
352355
},
353356

354-
__loadView: function(view, opts) {
357+
__loadView: function(view, opts, clearUrl=true) {
355358
const options = {
356359
top: 0,
357360
bottom: 0,
@@ -384,7 +387,9 @@ qx.Class.define("osparc.Application", {
384387
}
385388

386389
// Clear URL
387-
window.history.replaceState(null, "", "/");
390+
if (clearUrl) {
391+
window.history.replaceState(null, "", "/");
392+
}
388393
},
389394

390395
/**

services/web/client/source/class/osparc/component/form/json/JsonSchemaForm.js

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,37 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
2525
]);
2626
ajvLoader.addListener("ready", e => {
2727
this.__ajv = new Ajv();
28-
osparc.utils.Utils.fetchJSON(schemaUrl)
29-
.then(schema => {
30-
if (this.__validate(schema.$schema, schema)) {
31-
// If schema is valid
32-
if (data && this.__validate(schema, data)) {
33-
// Validate data if present
34-
this.__data = data;
28+
if (schemaUrl) {
29+
osparc.utils.Utils.fetchJSON(schemaUrl)
30+
.then(schema => {
31+
if (this.__validate(schema.$schema, schema)) {
32+
// If schema is valid
33+
this.__schema = schema;
34+
if (data && this.__validate(this.__schema, data)) {
35+
// Data is valid
36+
this.__data = data;
37+
}
38+
return this.__schema;
3539
}
36-
return schema;
37-
}
38-
return null;
39-
})
40-
.then(this.__render)
41-
.catch(err => {
42-
console.error(err);
43-
this.__render(null);
44-
});
40+
return null;
41+
})
42+
.then(this.__render)
43+
.catch(err => {
44+
console.error(err);
45+
});
46+
}
47+
if (data) {
48+
this.setData(data);
49+
}
50+
this.fireEvent("ajvReady");
4551
}, this);
4652
ajvLoader.addListener("failed", console.error, this);
4753
this.__render = this.__render.bind(this);
4854
ajvLoader.start();
4955
},
5056
events: {
5157
"ready": "qx.event.type.Event",
58+
"ajvReady": "qx.event.type.Event",
5259
"submit": "qx.event.type.Data"
5360
},
5461
members: {
@@ -72,7 +79,7 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
7279
this.__submitBtn = new osparc.ui.form.FetchButton(this.tr("Submit"));
7380
this.__submitBtn.addListener("execute", () => {
7481
if (this.__isValidData()) {
75-
const formData = this.toObject();
82+
const formData = this.toObject(schema);
7683
if (this.__validate(schema, formData.json)) {
7784
this.fireDataEvent("submit", formData);
7885
}
@@ -131,7 +138,7 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
131138
// Arrays allow to create new items with a button
132139
const arrayContainer = this.__expandArray(schema, data, depth);
133140
container.add(arrayContainer);
134-
const addButton = new qx.ui.form.Button(`Add ${objectPath.get(schema, "items.title", key)}`, "@FontAwesome5Solid/plus-circle/14");
141+
const addButton = new qx.ui.form.Button(`Add ${objectPath.get(schema, "items.title", schema.title || key)}`, "@FontAwesome5Solid/plus-circle/14");
135142
addButton.addListener("execute", () => {
136143
// key = -1 for an array item. we let JsonSchemaFormArray manage the array keys
137144
arrayContainer.add(this.__expand(-1, schema.items, null, depth+1));
@@ -141,7 +148,8 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
141148
// Leaf
142149
const input = container.addInput(validation, this.__validationManager);
143150
if (data) {
144-
input.setValue(data);
151+
const isNumber = ["number", "integer"].includes(schema.type);
152+
input.setValue(isNumber ? String(data) : data);
145153
}
146154
this.__inputItems.push(container);
147155
}
@@ -178,17 +186,24 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
178186
} else {
179187
container.setLayout(new qx.ui.layout.VBox());
180188
}
181-
Object.entries(schema.properties).forEach(([key, value]) => container.add(this.__expand(key, value, data ? data[key] : data, depth+1, {
182-
required: schema.required && schema.required.includes(key)
183-
})));
189+
Object.entries(schema.properties).forEach(([key, value], index) => {
190+
const allProps = Object.values(schema.properties);
191+
const nextProp = index < allProps.length - 1 ? allProps[index+1] : null;
192+
container.add(this.__expand(key, value, data ? data[key] : data, depth+1, {
193+
required: schema.required && schema.required.includes(key)
194+
}), {
195+
lineBreak: nextProp && nextProp.type === "array" || value.type === "array",
196+
stretch: value.type === "array"
197+
});
198+
});
184199
return container;
185200
},
186201
/**
187202
* Uses objectPath library to construct a JS object with the values from the inputs.
188203
*/
189-
toObject: function() {
204+
toObject: function(schema) {
190205
const obj = {
191-
json: {}
206+
json: schema.type === "array" ? [] : {}
192207
};
193208
const inputMap = {};
194209
// Retrieve paths
@@ -204,6 +219,7 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
204219
// Construct object
205220
Object.entries(inputMap).forEach(([path, item]) => {
206221
const input = item.getInput();
222+
const type = item.getType();
207223
if (input instanceof osparc.ui.form.FileInput) {
208224
obj.files = obj.files || [];
209225
if (input.getFile()) {
@@ -212,7 +228,8 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
212228
}
213229
const value = input.getValue();
214230
if (typeof value !== "undefined" && value !== null) {
215-
objectPath.set(obj.json, path, input.getValue());
231+
const isNumber = ["number", "integer"].includes(type);
232+
objectPath.set(obj.json, path, isNumber ? Number(input.getValue()) : input.getValue());
216233
}
217234
});
218235
return obj;
@@ -248,6 +265,30 @@ qx.Class.define("osparc.component.form.json.JsonSchemaForm", {
248265
}
249266
});
250267
return this.__validationManager.validate();
268+
},
269+
setSchema: function(schema) {
270+
if (this.__validate(schema.$schema, schema)) {
271+
// If schema is valid
272+
this.__schema = schema;
273+
if (this.__data && !this.__validate(this.__schema, this.__data)) {
274+
// Data is invalid
275+
this.__data = null;
276+
}
277+
} else {
278+
this.__schema = null;
279+
}
280+
this.__render(this.__schema);
281+
},
282+
setData: function(data) {
283+
if (data && this.__validate(this.__schema, data)) {
284+
// Data is valid
285+
this.__data = data;
286+
this.__render(this.__schema);
287+
return;
288+
}
289+
if (this.__validate(this.__schema, this.__data)) {
290+
this.__render(this.__schema);
291+
}
251292
}
252293
}
253294
});

services/web/client/source/class/osparc/component/form/json/JsonSchemaFormItem.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/**
99
* A generic form item to be used inside JsonSchemaForm.
1010
* It generates an appropriate header than can dynamically change for array items (if the user deletes one element).
11-
* Manages this dynamic keys to update the header and generate consistent output data object.
11+
* Manages this dynamic keys to update the header and generate a consistent output data object.
1212
*/
1313
qx.Class.define("osparc.component.form.json.JsonSchemaFormItem", {
1414
extend: qx.ui.container.Composite,
@@ -62,6 +62,12 @@ qx.Class.define("osparc.component.form.json.JsonSchemaFormItem", {
6262
getInput: function() {
6363
return this.__input;
6464
},
65+
/**
66+
* Returns the type of this input
67+
*/
68+
getType: function() {
69+
return this.__schema.type;
70+
},
6571
/**
6672
* Function that recursively constructs the path of this form item.
6773
*/
@@ -83,7 +89,7 @@ qx.Class.define("osparc.component.form.json.JsonSchemaFormItem", {
8389
return "orphan.osparc.form";
8490
},
8591
/**
86-
* Function that returns an appropriate widget fot the given type.
92+
* Function that returns an appropriate widget for the given type.
8793
*
8894
* @param {String} type Type of the input that will be used to determine the render behavior
8995
*/
@@ -111,7 +117,7 @@ qx.Class.define("osparc.component.form.json.JsonSchemaFormItem", {
111117
// falls through
112118
default:
113119
input = new qx.ui.form.TextField().set({
114-
required: validation && validation.required
120+
required: Boolean(validation && validation.required)
115121
});
116122
if (this.__schema.pattern) {
117123
validationManager.add(input, osparc.utils.Validators.regExp(RegExp(this.__schema.pattern)));
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* oSPARC - The SIMCORE frontend - https://osparc.io
3+
* Copyright: 2022 IT'IS Foundation - https://itis.swiss
4+
* License: MIT - https://opensource.org/licenses/MIT
5+
* Author: Ignacio Pascual (ignapas)
6+
*/
7+
8+
/**
9+
* @asset(form/test1.json)
10+
*/
11+
12+
qx.Class.define("osparc.desktop.FormSandboxPage", {
13+
extend: qx.ui.container.Composite,
14+
construct: function() {
15+
this.base();
16+
this.__buildLayout();
17+
this.__attachEventHandlers();
18+
osparc.utils.Utils.fetchJSON("/resource/form/test1.json")
19+
.then(schema => {
20+
const stringData = JSON.stringify(schema, null, 4);
21+
this.__schema.setValue(stringData);
22+
this.__form.addListener("ajvReady", () => {
23+
this.__form.setSchema(schema);
24+
});
25+
});
26+
},
27+
members: {
28+
__schema: null,
29+
__data: null,
30+
__jsonData: null,
31+
__buildLayout: function() {
32+
this.setPadding(100);
33+
this.setLayout(new qx.ui.layout.HBox(50));
34+
const schemaAndData = new qx.ui.container.Composite();
35+
schemaAndData.setLayout(new qx.ui.layout.VBox(20));
36+
this.__schema = new qx.ui.form.TextArea();
37+
this.__data = new qx.ui.form.TextArea();
38+
schemaAndData.add(this.__schema, {height: "50%"});
39+
schemaAndData.add(this.__data, {height: "50%"});
40+
this.add(schemaAndData, {width: "50%"});
41+
this.__form = new osparc.component.form.json.JsonSchemaForm();
42+
const scrollContainer = new qx.ui.container.Scroll();
43+
scrollContainer.add(this.__form);
44+
this.add(scrollContainer, {width: "50%"});
45+
},
46+
__attachEventHandlers: function() {
47+
this.__schema.addListener("input", data => {
48+
try {
49+
const jsonSchema = JSON.parse(data.getData());
50+
this.__form.setSchema(jsonSchema);
51+
this.__form.setData(this.__jsonData);
52+
} catch (e) {
53+
this.__form.setSchema(null);
54+
}
55+
});
56+
this.__data.addListener("input", data => {
57+
try {
58+
const jsonData = JSON.parse(data.getData());
59+
this.__form.setData(jsonData);
60+
this.__jsonData = jsonData;
61+
} catch (e) {
62+
console.error(e);
63+
this.__form.setData(null);
64+
}
65+
});
66+
this.__form.addListener("submit", data => {
67+
const stringData = JSON.stringify(data.getData().json, null, 4);
68+
this.__data.setValue(stringData);
69+
});
70+
}
71+
}
72+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "Input Port 1",
4+
"type": "array",
5+
"items": {
6+
"type": "object",
7+
"title": "input",
8+
"properties": {
9+
"i": {
10+
"title": "Integer",
11+
"exclusiveMinimum": 3,
12+
"type": "integer"
13+
},
14+
"b": {
15+
"title": "Bool",
16+
"default": false,
17+
"type": "boolean"
18+
},
19+
"s": {
20+
"title": "String",
21+
"type": "string"
22+
},
23+
"l": {
24+
"title": "Array Ints",
25+
"type": "array",
26+
"items": {
27+
"title": "int",
28+
"type": "integer"
29+
}
30+
}
31+
},
32+
"required": [
33+
"i",
34+
"s"
35+
]
36+
}
37+
}

0 commit comments

Comments
 (0)