Added button group support to Formblock and Tableblock components

This commit is contained in:
Dean 2023-11-23 10:45:13 +00:00
parent 105e9db00e
commit c0012409f7
6 changed files with 243 additions and 128 deletions

View File

@ -601,6 +601,27 @@ export const getFrontendStore = () => {
// Finally try an external table
return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL)
},
processNestedSettings: enrichedComponent => {
const componentPrefix = "@budibase/standard-components"
if (enrichedComponent?._component == `${componentPrefix}/formblock`) {
// Use default config if the 'buttons' prop has never been initialised
if (!("buttons" in enrichedComponent)) {
enrichedComponent["buttons"] =
Utils.buildDynamicButtonConfig(enrichedComponent)
//Ensure existing Formblocks position their buttons at the top.
enrichedComponent["buttonPosition"] = "top"
} else if (enrichedComponent["buttons"] == null) {
// Ignore legacy config if 'buttons' has been reset by 'resetOn'
const { _id, actionType, dataSource } = enrichedComponent
enrichedComponent["buttons"] = Utils.buildDynamicButtonConfig({
_id,
actionType,
dataSource,
})
}
}
},
enrichEmptySettings: (component, opts) => {
if (!component?._component) {
return
@ -672,7 +693,6 @@ export const getFrontendStore = () => {
component[setting.key] = setting.defaultValue
}
}
// Validate non-empty settings
else {
if (setting.type === "dataProvider") {
@ -722,6 +742,9 @@ export const getFrontendStore = () => {
useDefaultValues: true,
})
// Process nested component settings
store.actions.components.processNestedSettings(instance)
// Add any extra properties the component needs
let extras = {}
if (definition.hasChildren) {
@ -1247,9 +1270,13 @@ export const getFrontendStore = () => {
const settings = getComponentSettings(component._component)
const updatedSetting = settings.find(setting => setting.key === name)
const resetFields = settings.filter(
setting => name === setting.resetOn
)
// Can be a single string or array of strings
const resetFields = settings.filter(setting => {
return (
name === setting.resetOn ||
(Array.isArray(setting.resetOn) && setting.resetOn.includes(name))
)
})
resetFields?.forEach(setting => {
component[setting.key] = null
})
@ -1271,6 +1298,9 @@ export const getFrontendStore = () => {
})
}
component[name] = value
// Process nested component settings
store.actions.components.processNestedSettings(component)
}
},
requestEjectBlock: componentId => {
@ -1278,7 +1308,7 @@ export const getFrontendStore = () => {
},
handleEjectBlock: async (componentId, ejectedDefinition) => {
let nextSelectedComponentId
console.log("EJECTING")
await store.actions.screens.patch(screen => {
const block = findComponent(screen.props, componentId)
const parent = findComponentParent(screen.props, componentId)

View File

@ -19,6 +19,8 @@
export let includeHidden = false
export let tag
$: store.actions.components.processNestedSettings(componentInstance)
$: sections = getSections(
componentInstance,
componentDefinition,

View File

@ -5,6 +5,7 @@
import BlockComponent from "components/BlockComponent.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
import { Utils } from "@budibase/frontend-core"
export let title
export let dataSource
@ -33,6 +34,7 @@
export let notificationOverride
const { fetchDatasourceSchema, API } = getContext("sdk")
const component = getContext("component")
const stateKey = `ID_${generate()}`
let formId
@ -259,16 +261,25 @@
name="Details form block"
type="formblock"
bind:id={detailsFormBlockId}
context="form-edit"
props={{
dataSource,
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
deleteButtonLabel: deleteLabel,
buttonPosition: "top",
buttons: Utils.buildDynamicButtonConfig({
_id: $component.id + "-form-edit",
showDeleteButton: deleteLabel !== "",
showSaveButton: true,
saveButtonLabel: sidePanelSaveLabel || "Save",
deleteButtonLabel: deleteLabel,
notificationOverride,
actionType: "Update",
dataSource,
}),
actionType: "Update",
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
fields: sidePanelFields || normalFields,
title: editTitle,
labelPosition: "left",
notificationOverride,
}}
/>
</BlockComponent>
@ -284,16 +295,23 @@
<BlockComponent
name="New row form block"
type="formblock"
context="form-new"
props={{
dataSource,
showSaveButton: true,
showDeleteButton: false,
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
buttonPosition: "top",
buttons: Utils.buildDynamicButtonConfig({
_id: $component.id + "-form-new",
showDeleteButton: false,
showSaveButton: true,
saveButtonLabel: "Save",
notificationOverride,
actionType: "Create",
dataSource,
}),
actionType: "Create",
fields: sidePanelFields || normalFields,
title: "Create Row",
labelPosition: "left",
notificationOverride,
}}
/>
</BlockComponent>

View File

@ -4,28 +4,31 @@
import Block from "components/Block.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import InnerFormBlock from "./InnerFormBlock.svelte"
import { Utils } from "@budibase/frontend-core"
export let actionType
export let dataSource
export let size
export let disabled
export let fields
export let buttons
export let buttonPosition
export let title
export let description
export let showDeleteButton
export let showSaveButton
export let saveButtonLabel
export let deleteButtonLabel
export let rowId
export let actionUrl
export let noRowsMessage
export let notificationOverride
// Accommodate old config to ensure delete button does not reappear
$: deleteLabel = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
$: saveLabel = showSaveButton === false ? "" : saveButtonLabel?.trim()
// Legacy
export let showDeleteButton
export let showSaveButton
export let saveButtonLabel
export let deleteButtonLabel
const { fetchDatasourceSchema } = getContext("sdk")
const component = getContext("component")
const convertOldFieldFormat = fields => {
if (!fields) {
@ -98,11 +101,23 @@
fields: fieldsOrDefault,
title,
description,
saveButtonLabel: saveLabel,
deleteButtonLabel: deleteLabel,
schema,
repeaterId,
notificationOverride,
buttons:
buttons ||
Utils.buildDynamicButtonConfig({
_id: $component.id,
showDeleteButton,
showSaveButton,
saveButtonLabel,
deleteButtonLabel,
notificationOverride,
actionType,
actionUrl,
dataSource,
}),
buttonPosition,
}
const fetchSchema = async () => {
schema = (await fetchDatasourceSchema(dataSource)) || {}

View File

@ -1,22 +1,18 @@
<script>
import BlockComponent from "components/BlockComponent.svelte"
import Placeholder from "components/app/Placeholder.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import { getContext } from "svelte"
export let dataSource
export let actionUrl
export let actionType
export let size
export let disabled
export let fields
export let title
export let description
export let saveButtonLabel
export let deleteButtonLabel
export let buttons = []
export let buttonPosition = "bottom"
export let schema
export let repeaterId
export let notificationOverride
const FieldTypeToComponentMap = {
string: "stringfield",
@ -37,74 +33,7 @@
let formId
$: onSave = [
{
"##eventHandlerType": "Validate Form",
parameters: {
componentId: formId,
},
},
{
"##eventHandlerType": "Save Row",
parameters: {
providerId: formId,
tableId: dataSource?.resourceId,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
// Clear a create form once submitted
...(actionType !== "Create"
? []
: [
{
"##eventHandlerType": "Clear Form",
parameters: {
componentId: formId,
},
},
]),
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: onDelete = [
{
"##eventHandlerType": "Delete Row",
parameters: {
confirm: true,
tableId: dataSource?.resourceId,
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: renderDeleteButton = deleteButtonLabel && actionType === "Update"
$: renderSaveButton = saveButtonLabel && actionType !== "View"
$: renderButtons = renderDeleteButton || renderSaveButton
$: renderHeader = renderButtons || title
$: renderHeader = buttons || title
const getComponentForField = field => {
const fieldSchemaName = field.field || field.name
@ -184,42 +113,14 @@
props={{ text: title || "" }}
order={0}
/>
{#if renderButtons}
{#if buttonPosition == "top"}
<BlockComponent
type="container"
type="buttongroup"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "center",
gap: "M",
wrap: true,
buttons,
}}
order={1}
>
{#if renderDeleteButton}
<BlockComponent
type="button"
props={{
text: deleteButtonLabel,
onClick: onDelete,
quiet: true,
type: "secondary",
}}
order={0}
/>
{/if}
{#if renderSaveButton}
<BlockComponent
type="button"
props={{
text: saveButtonLabel,
onClick: onSave,
type: "cta",
}}
order={1}
/>
{/if}
</BlockComponent>
order={0}
/>
{/if}
</BlockComponent>
</BlockComponent>
@ -245,6 +146,20 @@
</BlockComponent>
{/key}
</BlockComponent>
{#if buttonPosition === "bottom"}
<BlockComponent
type="buttongroup"
props={{
buttons,
}}
styles={{
normal: {
"margin-top": "16",
},
}}
order={1}
/>
{/if}
</BlockComponent>
{:else}
<Placeholder

View File

@ -1,3 +1,6 @@
import { makePropSafe as safe } from "@budibase/string-templates"
import { Helpers } from "@budibase/bbui"
/**
* Utility to wrap an async function and ensure all invocations happen
* sequentially.
@ -106,3 +109,135 @@ export const domDebounce = callback => {
}
}
}
/**
* Build the default FormBlock button configs per actionType
* Parse any legacy button config and mirror its the outcome
*
* @param {any} props
* */
export const buildDynamicButtonConfig = props => {
const {
_id,
actionType,
dataSource,
notificationOverride,
actionUrl,
showDeleteButton,
deleteButtonLabel,
showSaveButton,
saveButtonLabel,
} = props || {}
if (!_id) {
console.log("MISSING ID")
return
}
const formId = `${_id}-form`
const repeaterId = `${_id}-repeater`
const resourceId = dataSource?.resourceId
// Accommodate old config to ensure delete button does not reappear
const deleteText = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
const saveText = showSaveButton === false ? "" : saveButtonLabel?.trim()
const onSave = [
{
"##eventHandlerType": "Validate Form",
parameters: {
componentId: formId,
},
},
{
"##eventHandlerType": "Save Row",
parameters: {
providerId: formId,
tableId: resourceId,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
// Clear a create form once submitted
...(actionType !== "Create"
? []
: [
{
"##eventHandlerType": "Clear Form",
parameters: {
componentId: formId,
},
},
]),
...(actionUrl
? [
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
: []),
]
const onDelete = [
{
"##eventHandlerType": "Delete Row",
parameters: {
confirm: true,
tableId: resourceId,
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
...(actionUrl
? [
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
: []),
]
const defaultButtons = []
if (["Update", "Create"].includes(actionType) && showSaveButton !== false) {
defaultButtons.push({
text: saveText || "Save",
_id: Helpers.uuid(),
_component: "@budibase/standard-components/button",
onClick: onSave,
type: "cta",
})
}
if (actionType == "Update" && showDeleteButton !== false) {
defaultButtons.push({
text: deleteText || "Delete",
_id: Helpers.uuid(),
_component: "@budibase/standard-components/button",
onClick: onDelete,
quiet: true,
type: "secondary",
})
}
return defaultButtons
}