Merge pull request #8608 from Budibase/fix/form-block

Fix form block issues
This commit is contained in:
Andrew Kingston 2022-11-17 10:28:33 +00:00 committed by GitHub
commit abaa83d4ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 333 additions and 260 deletions

View File

@ -23,14 +23,18 @@
export let bindings = [] export let bindings = []
export let nested export let nested
$: showAvailableActions = !actions?.length
let actionQuery let actionQuery
$: parsedQuery =
typeof actionQuery === "string" ? actionQuery.toLowerCase().trim() : ""
let selectedAction = actions?.length ? actions[0] : null let selectedAction = actions?.length ? actions[0] : null
$: {
// Ensure parameters object is never null
if (selectedAction && !selectedAction.parameters) {
selectedAction.parameters = {}
}
}
$: parsedQuery =
typeof actionQuery === "string" ? actionQuery.toLowerCase().trim() : ""
$: showAvailableActions = !actions?.length
$: mappedActionTypes = actionTypes.reduce((acc, action) => { $: mappedActionTypes = actionTypes.reduce((acc, action) => {
let parsedName = action.name.toLowerCase().trim() let parsedName = action.name.toLowerCase().trim()
if (parsedQuery.length && parsedName.indexOf(parsedQuery) < 0) { if (parsedQuery.length && parsedName.indexOf(parsedQuery) < 0) {
@ -40,7 +44,6 @@
acc[action.type].push(action) acc[action.type].push(action)
return acc return acc
}, {}) }, {})
// These are ephemeral bindings which only exist while executing actions // These are ephemeral bindings which only exist while executing actions
$: eventContexBindings = getEventContextBindings( $: eventContexBindings = getEventContextBindings(
$currentAsset, $currentAsset,
@ -50,9 +53,8 @@
selectedAction?.id selectedAction?.id
) )
$: allBindings = eventContexBindings.concat(bindings) $: allBindings = eventContexBindings.concat(bindings)
// Assign a unique ID to each action
$: { $: {
// Ensure each action has a unique ID
if (actions) { if (actions) {
actions.forEach(action => { actions.forEach(action => {
if (!action.id) { if (!action.id) {
@ -61,13 +63,11 @@
}) })
} }
} }
$: selectedActionComponent = $: selectedActionComponent =
selectedAction && selectedAction &&
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_KEY])?.component actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_KEY])?.component
// Select the first action if we delete an action
$: { $: {
// Select the first action if we delete an action
if (selectedAction && !actions?.includes(selectedAction)) { if (selectedAction && !actions?.includes(selectedAction)) {
selectedAction = actions?.[0] selectedAction = actions?.[0]
} }

View File

@ -1,14 +1,16 @@
<script> <script>
import { Label, Body } from "@budibase/bbui" import { Label } from "@budibase/bbui"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
export let parameters export let parameters
export let bindings = [] export let bindings = []
</script> </script>
<Body size="S">Navigate To screen, or leave blank.</Body>
<br />
<div class="root"> <div class="root">
<span>
You can optionally navigate to another screen after closing the screen
modal.
</span>
<Label small>Screen</Label> <Label small>Screen</Label>
<DrawerBindableInput <DrawerBindableInput
title="Destination URL" title="Destination URL"
@ -20,6 +22,9 @@
</div> </div>
<style> <style>
span {
grid-column: 1 / 3;
}
.root { .root {
display: grid; display: grid;
align-items: center; align-items: center;

View File

@ -9,7 +9,7 @@
let structureLookupMap = {} let structureLookupMap = {}
const registerBlockComponent = (id, order, parentId, instance) => { const registerBlockComponent = (id, order, parentId, instance) => {
// Ensure child array exists // Ensure child map exists
if (!structureLookupMap[parentId]) { if (!structureLookupMap[parentId]) {
structureLookupMap[parentId] = {} structureLookupMap[parentId] = {}
} }
@ -18,6 +18,14 @@
structureLookupMap[parentId][order] = instance structureLookupMap[parentId][order] = instance
} }
const unregisterBlockComponent = (order, parentId) => {
// Ensure child map exists
if (!structureLookupMap[parentId]) {
return
}
delete structureLookupMap[parentId][order]
}
const eject = () => { const eject = () => {
// Start the new structure with the root component // Start the new structure with the root component
let definition = structureLookupMap[$component.id][0] let definition = structureLookupMap[$component.id][0]
@ -73,6 +81,7 @@
// We register block components with their raw props so that we can eject // We register block components with their raw props so that we can eject
// blocks later on // blocks later on
registerComponent: registerBlockComponent, registerComponent: registerBlockComponent,
unregisterComponent: unregisterBlockComponent,
}) })
onMount(() => { onMount(() => {

View File

@ -1,5 +1,5 @@
<script> <script>
import { getContext } from "svelte" import { getContext, onDestroy } from "svelte"
import { generate } from "shortid" import { generate } from "shortid"
import { builderStore } from "../stores/builder.js" import { builderStore } from "../stores/builder.js"
import Component from "components/Component.svelte" import Component from "components/Component.svelte"
@ -41,6 +41,12 @@
block.registerComponent(id, order ?? 0, $component?.id, instance) block.registerComponent(id, order ?? 0, $component?.id, instance)
} }
} }
onDestroy(() => {
if ($builderStore.inBuilder) {
block.unregisterComponent(order ?? 0, $component?.id)
}
})
</script> </script>
<Component {instance} isBlock> <Component {instance} isBlock>

View File

@ -1,242 +0,0 @@
<script>
import { getContext } from "svelte"
import BlockComponent from "../../BlockComponent.svelte"
import Block from "../../Block.svelte"
import Placeholder from "../Placeholder.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
export let actionType
export let dataSource
export let size
export let disabled
export let fields
export let labelPosition
export let title
export let showSaveButton
export let showDeleteButton
export let rowId
export let actionUrl
const { fetchDatasourceSchema } = getContext("sdk")
const FieldTypeToComponentMap = {
string: "stringfield",
number: "numberfield",
options: "optionsfield",
array: "multifieldselect",
boolean: "booleanfield",
longform: "longformfield",
datetime: "datetimefield",
attachment: "attachmentfield",
link: "relationshipfield",
json: "jsonfield",
barcodeqr: "codescanner",
}
let schema
let formId
let providerId
let repeaterId
$: fetchSchema(dataSource)
$: onSave = [
{
"##eventHandlerType": "Save Row",
parameters: {
providerId: formId,
tableId: dataSource?.tableId,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: onDelete = [
{
"##eventHandlerType": "Delete Row",
parameters: {
confirm: true,
tableId: dataSource?.tableId,
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: filter = [
{
field: "_id",
operator: "equal",
type: "string",
value: !rowId ? `{{ ${safe("url")}.${safe("id")} }}` : rowId,
valueType: "Binding",
},
]
$: dataProvider = `{{ literal ${safe(providerId)} }}`
$: renderDeleteButton = showDeleteButton && actionType === "Update"
$: renderSaveButton = showSaveButton && actionType !== "View"
$: renderButtons = renderDeleteButton || renderSaveButton
$: renderHeader = renderButtons || title
const fetchSchema = async () => {
schema = (await fetchDatasourceSchema(dataSource)) || {}
}
const getComponentForField = field => {
if (!field || !schema?.[field]) {
return null
}
const type = schema[field].type
return FieldTypeToComponentMap[type]
}
</script>
<Block>
{#if fields?.length}
<BlockComponent
type="dataprovider"
context="provider"
bind:id={providerId}
props={{
dataSource,
filter,
limit: 1,
paginate: false,
}}
>
<BlockComponent
type="repeater"
context="repeater"
bind:id={repeaterId}
props={{
dataProvider,
noRowsMessage: "We couldn't find a row to display",
direction: "column",
hAlign: "center",
}}
>
<BlockComponent
type="form"
props={{
actionType: actionType === "Create" ? "Create" : "Update",
dataSource,
size,
disabled: disabled || actionType === "View",
}}
styles={{
normal: {
width: "600px",
},
}}
context="form"
bind:id={formId}
>
<BlockComponent
type="container"
props={{
direction: "column",
hAlign: "stretch",
vAlign: "top",
gap: "M",
}}
>
{#if renderHeader}
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "center",
gap: "M",
wrap: true,
}}
order={0}
>
<BlockComponent
type="heading"
props={{ text: title || "" }}
order={0}
/>
{#if renderButtons}
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "center",
gap: "M",
wrap: true,
}}
order={1}
>
{#if renderDeleteButton}
<BlockComponent
type="button"
props={{
text: "Delete",
onClick: onDelete,
quiet: true,
type: "secondary",
}}
order={0}
/>
{/if}
{#if renderSaveButton}
<BlockComponent
type="button"
props={{
text: "Save",
onClick: onSave,
type: "cta",
}}
order={1}
/>
{/if}
</BlockComponent>
{/if}
</BlockComponent>
{/if}
<BlockComponent
type="fieldgroup"
props={{ labelPosition }}
order={1}
>
{#each fields as field, idx}
{#if getComponentForField(field)}
<BlockComponent
type={getComponentForField(field)}
props={{
field,
label: field,
placeholder: field,
disabled,
}}
order={idx}
/>
{/if}
{/each}
</BlockComponent>
</BlockComponent>
</BlockComponent>
</BlockComponent>
</BlockComponent>
{:else}
<Placeholder
text="Choose your table and add some fields to your form to get started"
/>
{/if}
</Block>

View File

@ -0,0 +1,101 @@
<script>
import { getContext } from "svelte"
import BlockComponent from "components/BlockComponent.svelte"
import Block from "components/Block.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import InnerFormBlock from "./InnerFormBlock.svelte"
export let actionType
export let dataSource
export let size
export let disabled
export let fields
export let labelPosition
export let title
export let showSaveButton
export let showDeleteButton
export let rowId
export let actionUrl
const { fetchDatasourceSchema } = getContext("sdk")
let schema
let providerId
let repeaterId
$: fetchSchema(dataSource)
$: dataProvider = `{{ literal ${safe(providerId)} }}`
$: filter = [
{
field: "_id",
operator: "equal",
type: "string",
value: !rowId ? `{{ ${safe("url")}.${safe("id")} }}` : rowId,
valueType: "Binding",
},
]
// We could simply spread $$props into the inner form and append our
// additions, but that would create svelte warnings about unused props and
// make maintenance in future more confusing as we typically always have a
// proper mapping of schema settings to component exports, without having to
// search multiple files
$: innerProps = {
dataSource,
actionUrl,
actionType,
size,
disabled,
fields,
labelPosition,
title,
showSaveButton,
showDeleteButton,
schema,
repeaterId,
}
const fetchSchema = async () => {
schema = (await fetchDatasourceSchema(dataSource)) || {}
}
</script>
<Block>
{#if actionType === "Create"}
<BlockComponent
type="container"
props={{
direction: "column",
hAlign: "left",
vAlign: "stretch",
}}
>
<InnerFormBlock {...innerProps} />
</BlockComponent>
{:else}
<BlockComponent
type="dataprovider"
context="provider"
bind:id={providerId}
props={{
dataSource,
filter,
limit: 1,
paginate: false,
}}
>
<BlockComponent
type="repeater"
context="repeater"
bind:id={repeaterId}
props={{
dataProvider,
noRowsMessage: "We couldn't find a row to display",
direction: "column",
hAlign: "center",
}}
>
<InnerFormBlock {...innerProps} />
</BlockComponent>
</BlockComponent>
{/if}
</Block>

View File

@ -0,0 +1,194 @@
<script>
import BlockComponent from "components/BlockComponent.svelte"
import Placeholder from "components/app/Placeholder.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
export let dataSource
export let actionUrl
export let actionType
export let size
export let disabled
export let fields
export let labelPosition
export let title
export let showSaveButton
export let showDeleteButton
export let schema
export let repeaterId
const FieldTypeToComponentMap = {
string: "stringfield",
number: "numberfield",
options: "optionsfield",
array: "multifieldselect",
boolean: "booleanfield",
longform: "longformfield",
datetime: "datetimefield",
attachment: "attachmentfield",
link: "relationshipfield",
json: "jsonfield",
barcodeqr: "codescanner",
}
let formId
$: onSave = [
{
"##eventHandlerType": "Save Row",
parameters: {
providerId: formId,
tableId: dataSource?.tableId,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: onDelete = [
{
"##eventHandlerType": "Delete Row",
parameters: {
confirm: true,
tableId: dataSource?.tableId,
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: renderDeleteButton = showDeleteButton && actionType === "Update"
$: renderSaveButton = showSaveButton && actionType !== "View"
$: renderButtons = renderDeleteButton || renderSaveButton
$: renderHeader = renderButtons || title
const getComponentForField = field => {
if (!field || !schema?.[field]) {
return null
}
const type = schema[field].type
return FieldTypeToComponentMap[type]
}
</script>
{#if fields?.length}
<BlockComponent
type="form"
props={{
actionType: actionType === "Create" ? "Create" : "Update",
dataSource,
size,
disabled: disabled || actionType === "View",
}}
styles={{
normal: {
width: "600px",
"margin-left": "auto",
"margin-right": "auto",
},
}}
context="form"
bind:id={formId}
>
<BlockComponent
type="container"
props={{
direction: "column",
hAlign: "stretch",
vAlign: "top",
gap: "M",
}}
>
{#if renderHeader}
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "center",
gap: "M",
wrap: true,
}}
order={0}
>
<BlockComponent
type="heading"
props={{ text: title || "" }}
order={0}
/>
{#if renderButtons}
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "center",
gap: "M",
wrap: true,
}}
order={1}
>
{#if renderDeleteButton}
<BlockComponent
type="button"
props={{
text: "Delete",
onClick: onDelete,
quiet: true,
type: "secondary",
}}
order={0}
/>
{/if}
{#if renderSaveButton}
<BlockComponent
type="button"
props={{
text: "Save",
onClick: onSave,
type: "cta",
}}
order={1}
/>
{/if}
</BlockComponent>
{/if}
</BlockComponent>
{/if}
<BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}>
{#each fields as field, idx}
{#if getComponentForField(field)}
<BlockComponent
type={getComponentForField(field)}
props={{
field,
label: field,
placeholder: field,
disabled,
}}
order={idx}
/>
{/if}
{/each}
</BlockComponent>
</BlockComponent>
</BlockComponent>
{:else}
<Placeholder
text="Choose your table and add some fields to your form to get started"
/>
{/if}

View File

@ -1,5 +1,5 @@
export { default as tableblock } from "./TableBlock.svelte" export { default as tableblock } from "./TableBlock.svelte"
export { default as cardsblock } from "./CardsBlock.svelte" export { default as cardsblock } from "./CardsBlock.svelte"
export { default as repeaterblock } from "./RepeaterBlock.svelte" export { default as repeaterblock } from "./RepeaterBlock.svelte"
export { default as formblock } from "./FormBlock.svelte" export { default as formblock } from "./form/FormBlock.svelte"
export { default as chartblock } from "./ChartBlock.svelte" export { default as chartblock } from "./ChartBlock.svelte"

View File

@ -6,7 +6,7 @@
export let label export let label
export let placeholder export let placeholder
export let disabled = false export let disabled = false
export let enableTime = false export let enableTime = true
export let timeOnly = false export let timeOnly = false
export let time24hr = false export let time24hr = false
export let ignoreTimezones = false export let ignoreTimezones = false