Add new form field component for JSON fields, JSON validation and update autoscreen generation
This commit is contained in:
parent
1cbdf49b96
commit
b362068d47
|
@ -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) {
|
||||||
|
|
|
@ -43,7 +43,8 @@
|
||||||
"attachmentfield",
|
"attachmentfield",
|
||||||
"relationshipfield",
|
"relationshipfield",
|
||||||
"daterangepicker",
|
"daterangepicker",
|
||||||
"multifieldselect"
|
"multifieldselect",
|
||||||
|
"jsonfield"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
Loading…
Reference in New Issue