Add new form field component for JSON fields, JSON validation and update autoscreen generation

This commit is contained in:
Andrew Kingston 2021-12-03 09:20:45 +00:00
parent f2a2f5ac70
commit 1e38628a4b
9 changed files with 157 additions and 9 deletions

View File

@ -137,6 +137,7 @@ const fieldTypeToComponentMap = {
datetime: "datetimefield", datetime: "datetimefield",
attachment: "attachmentfield", attachment: "attachmentfield",
link: "relationshipfield", link: "relationshipfield",
json: "jsonfield",
} }
export function makeDatasourceFormComponents(datasource) { export function makeDatasourceFormComponents(datasource) {

View File

@ -43,7 +43,8 @@
"attachmentfield", "attachmentfield",
"relationshipfield", "relationshipfield",
"daterangepicker", "daterangepicker",
"multifieldselect" "multifieldselect",
"jsonfield"
] ]
}, },
{ {

View File

@ -45,6 +45,7 @@ const componentMap = {
"field/attachment": FormFieldSelect, "field/attachment": FormFieldSelect,
"field/link": FormFieldSelect, "field/link": FormFieldSelect,
"field/array": FormFieldSelect, "field/array": FormFieldSelect,
"field/json": FormFieldSelect,
// Some validation types are the same as others, so not all types are // Some validation types are the same as others, so not all types are
// explicitly listed here. e.g. options uses string validation // explicitly listed here. e.g. options uses string validation
"validation/string": ValidationEditor, "validation/string": ValidationEditor,

View File

@ -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": { "dataprovider": {
"name": "Data Provider", "name": "Data Provider",
"info": "Pagination is only available for data stored in tables.", "info": "Pagination is only available for data stored in tables.",

View File

@ -0,0 +1,71 @@
<script>
import { CoreTextArea } from "@budibase/bbui"
import Field from "./Field.svelte"
import { getContext } from "svelte"
export let field
export let label
export let placeholder
export let disabled = false
export let defaultValue = ""
const component = getContext("component")
const validation = [
{
constraint: "json",
type: "json",
error: "JSON syntax is invalid",
},
]
let fieldState
let fieldApi
$: height = $component.styles?.normal?.height || "124px"
const serialiseValue = value => {
return JSON.stringify(value || undefined, null, 4) || ""
}
const parseValue = value => {
try {
return JSON.parse(value)
} catch (error) {
return value
}
}
</script>
<Field
{label}
{field}
{disabled}
{validation}
{defaultValue}
type="json"
bind:fieldState
bind:fieldApi
>
{#if fieldState}
<div style="--height: {height};">
<CoreTextArea
value={serialiseValue(fieldState.value)}
on:change={e => fieldApi.setValue(parseValue(e.detail))}
disabled={fieldState.disabled}
error={fieldState.error}
id={fieldState.fieldId}
{placeholder}
/>
</div>
{/if}
</Field>
<style>
:global(.spectrum-Form-itemField .spectrum-Textfield--multiline) {
min-height: calc(var(--height) - 24px);
}
:global(.spectrum-Form--labelsAbove
.spectrum-Form-itemField
.spectrum-Textfield--multiline) {
min-height: calc(var(--height) - 24px);
}
</style>

View File

@ -1,6 +1,7 @@
<script> <script>
import { CoreTextArea } from "@budibase/bbui" import { CoreTextArea } from "@budibase/bbui"
import Field from "./Field.svelte" import Field from "./Field.svelte"
import { getContext } from "svelte"
export let field export let field
export let label export let label
@ -11,6 +12,9 @@
let fieldState let fieldState
let fieldApi let fieldApi
const component = getContext("component")
$: height = $component.styles?.normal?.height || "124px"
</script> </script>
<Field <Field
@ -24,6 +28,7 @@
bind:fieldApi bind:fieldApi
> >
{#if fieldState} {#if fieldState}
<div style="--height: {height};">
<CoreTextArea <CoreTextArea
value={fieldState.value} value={fieldState.value}
on:change={e => fieldApi.setValue(e.detail)} on:change={e => fieldApi.setValue(e.detail)}
@ -32,5 +37,17 @@
id={fieldState.fieldId} id={fieldState.fieldId}
{placeholder} {placeholder}
/> />
</div>
{/if} {/if}
</Field> </Field>
<style>
:global(.spectrum-Form-itemField .spectrum-Textfield--multiline) {
min-height: calc(var(--height) - 24px);
}
:global(.spectrum-Form--labelsAbove
.spectrum-Form-itemField
.spectrum-Textfield--multiline) {
min-height: calc(var(--height) - 24px);
}
</style>

View File

@ -11,3 +11,4 @@ export { default as attachmentfield } from "./AttachmentField.svelte"
export { default as relationshipfield } from "./RelationshipField.svelte" export { default as relationshipfield } from "./RelationshipField.svelte"
export { default as passwordfield } from "./PasswordField.svelte" export { default as passwordfield } from "./PasswordField.svelte"
export { default as formstep } from "./FormStep.svelte" export { default as formstep } from "./FormStep.svelte"
export { default as jsonfield } from "./JSONField.svelte"

View File

@ -206,6 +206,7 @@ const parseType = (value, type) => {
return value return value
} }
// Parse array, treating no elements as null
if (type === FieldTypes.ARRAY) { if (type === FieldTypes.ARRAY) {
if (!Array.isArray(value) || !value.length) { if (!Array.isArray(value) || !value.length) {
return null return null
@ -213,6 +214,12 @@ const parseType = (value, type) => {
return value 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 // If some unknown type, treat as null to avoid breaking validators
return null return null
} }
@ -290,6 +297,19 @@ const notContainsHandler = (value, rule) => {
return !containsHandler(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. * Map of constraint types to handlers.
*/ */
@ -306,6 +326,7 @@ const handlerMap = {
notRegex: notRegexHandler, notRegex: notRegexHandler,
contains: containsHandler, contains: containsHandler,
notContains: notContainsHandler, notContains: notContainsHandler,
json: jsonHandler,
} }
/** /**

View File

@ -13,6 +13,7 @@ export const FieldTypes = {
ATTACHMENT: "attachment", ATTACHMENT: "attachment",
LINK: "link", LINK: "link",
FORMULA: "formula", FORMULA: "formula",
JSON: "json",
} }
export const UnsortableTypes = [ export const UnsortableTypes = [