diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index 5b3bc041ff..4a6ef295ae 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -137,6 +137,7 @@ const fieldTypeToComponentMap = { datetime: "datetimefield", attachment: "attachmentfield", link: "relationshipfield", + json: "jsonfield", } export function makeDatasourceFormComponents(datasource) { diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index 240de3ea6c..4a6244ea68 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -43,7 +43,8 @@ "attachmentfield", "relationshipfield", "daterangepicker", - "multifieldselect" + "multifieldselect", + "jsonfield" ] }, { diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js index b9b227bef0..f1ace93337 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js @@ -45,6 +45,7 @@ const componentMap = { "field/attachment": FormFieldSelect, "field/link": FormFieldSelect, "field/array": FormFieldSelect, + "field/json": FormFieldSelect, // Some validation types are the same as others, so not all types are // explicitly listed here. e.g. options uses string validation "validation/string": ValidationEditor, diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 2478fa6b3b..c59c6a6ba9 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2418,6 +2418,40 @@ } ] }, + "jsonfield": { + "name": "Key/Value Field", + "icon": "Brackets", + "styles": ["size"], + "editable": true, + "settings": [ + { + "type": "field/json", + "label": "Field", + "key": "field" + }, + { + "type": "text", + "label": "Label", + "key": "label" + }, + { + "type": "text", + "label": "Placeholder", + "key": "placeholder" + }, + { + "type": "text", + "label": "Default value", + "key": "defaultValue" + }, + { + "type": "boolean", + "label": "Disabled", + "key": "disabled", + "defaultValue": false + } + ] + }, "dataprovider": { "name": "Data Provider", "info": "Pagination is only available for data stored in tables.", diff --git a/packages/client/src/components/app/forms/JSONField.svelte b/packages/client/src/components/app/forms/JSONField.svelte new file mode 100644 index 0000000000..d38a41b430 --- /dev/null +++ b/packages/client/src/components/app/forms/JSONField.svelte @@ -0,0 +1,71 @@ + + + + {#if fieldState} +
+ fieldApi.setValue(parseValue(e.detail))} + disabled={fieldState.disabled} + error={fieldState.error} + id={fieldState.fieldId} + {placeholder} + /> +
+ {/if} +
+ + diff --git a/packages/client/src/components/app/forms/LongFormField.svelte b/packages/client/src/components/app/forms/LongFormField.svelte index 81ad42bbcb..a58e1fe76c 100644 --- a/packages/client/src/components/app/forms/LongFormField.svelte +++ b/packages/client/src/components/app/forms/LongFormField.svelte @@ -1,6 +1,7 @@ {#if fieldState} - fieldApi.setValue(e.detail)} - disabled={fieldState.disabled} - error={fieldState.error} - id={fieldState.fieldId} - {placeholder} - /> +
+ fieldApi.setValue(e.detail)} + disabled={fieldState.disabled} + error={fieldState.error} + id={fieldState.fieldId} + {placeholder} + /> +
{/if}
+ + diff --git a/packages/client/src/components/app/forms/index.js b/packages/client/src/components/app/forms/index.js index f554f601d4..ab1f7d20ed 100644 --- a/packages/client/src/components/app/forms/index.js +++ b/packages/client/src/components/app/forms/index.js @@ -11,3 +11,4 @@ export { default as attachmentfield } from "./AttachmentField.svelte" export { default as relationshipfield } from "./RelationshipField.svelte" export { default as passwordfield } from "./PasswordField.svelte" export { default as formstep } from "./FormStep.svelte" +export { default as jsonfield } from "./JSONField.svelte" diff --git a/packages/client/src/components/app/forms/validation.js b/packages/client/src/components/app/forms/validation.js index f461ab00c0..4e06a640e9 100644 --- a/packages/client/src/components/app/forms/validation.js +++ b/packages/client/src/components/app/forms/validation.js @@ -206,6 +206,7 @@ const parseType = (value, type) => { return value } + // Parse array, treating no elements as null if (type === FieldTypes.ARRAY) { if (!Array.isArray(value) || !value.length) { return null @@ -213,6 +214,12 @@ const parseType = (value, type) => { return value } + // For JSON we don't touch the value at all as we want to verify it in its + // raw form + if (type === FieldTypes.JSON) { + return value + } + // If some unknown type, treat as null to avoid breaking validators return null } @@ -290,6 +297,19 @@ const notContainsHandler = (value, rule) => { return !containsHandler(value, rule) } +// Evaluates a constraint that the value must be a valid json object +const jsonHandler = value => { + if (typeof value !== "object" || Array.isArray(value)) { + return false + } + try { + JSON.parse(JSON.stringify(value)) + return true + } catch (error) { + return false + } +} + /** * Map of constraint types to handlers. */ @@ -306,6 +326,7 @@ const handlerMap = { notRegex: notRegexHandler, contains: containsHandler, notContains: notContainsHandler, + json: jsonHandler, } /** diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index 740f279b36..9d20177b52 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -13,6 +13,7 @@ export const FieldTypes = { ATTACHMENT: "attachment", LINK: "link", FORMULA: "formula", + JSON: "json", } export const UnsortableTypes = [