diff --git a/packages/builder/package.json b/packages/builder/package.json
index 09fd4dac48..267e4f5d72 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -63,7 +63,7 @@
}
},
"dependencies": {
- "@budibase/bbui": "^1.58.3",
+ "@budibase/bbui": "^1.58.5",
"@budibase/client": "^0.7.6",
"@budibase/colorpicker": "1.0.1",
"@budibase/string-templates": "^0.7.6",
diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index 79785f4e8d..abd8fdce85 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -1,7 +1,7 @@
import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store"
import { backendUiStore, store } from "builderStore"
-import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
+import { findComponentPath } from "./storeUtils"
import { makePropSafe } from "@budibase/string-templates"
import { TableNames } from "../constants"
@@ -12,9 +12,7 @@ const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
* Gets all bindable data context fields and instance fields.
*/
export const getBindableProperties = (rootComponent, componentId) => {
- const contextBindings = getContextBindings(rootComponent, componentId)
- const componentBindings = getComponentBindings(rootComponent)
- return [...contextBindings, ...componentBindings]
+ return getContextBindings(rootComponent, componentId)
}
/**
@@ -37,6 +35,30 @@ export const getDataProviderComponents = (rootComponent, componentId) => {
})
}
+/**
+ * Gets all data provider components above a component.
+ */
+export const getActionProviderComponents = (
+ rootComponent,
+ componentId,
+ actionType
+) => {
+ if (!rootComponent || !componentId) {
+ return []
+ }
+
+ // Get the component tree leading up to this component, ignoring the component
+ // itself
+ const path = findComponentPath(rootComponent, componentId)
+ path.pop()
+
+ // Filter by only data provider components
+ return path.filter(component => {
+ const def = store.actions.components.getDefinition(component._component)
+ return def?.actions?.includes(actionType)
+ })
+}
+
/**
* Gets a datasource object for a certain data provider component
*/
@@ -47,8 +69,9 @@ export const getDatasourceForProvider = component => {
}
// Extract datasource from component instance
+ const validSettingTypes = ["datasource", "table", "schema"]
const datasourceSetting = def.settings.find(setting => {
- return setting.type === "datasource" || setting.type === "table"
+ return validSettingTypes.includes(setting.type)
})
if (!datasourceSetting) {
return null
@@ -58,15 +81,14 @@ export const getDatasourceForProvider = component => {
// example an actual datasource object, or a table ID string.
// Convert the datasource setting into a proper datasource object so that
// we can use it properly
- if (datasourceSetting.type === "datasource") {
- return component[datasourceSetting?.key]
- } else if (datasourceSetting.type === "table") {
+ if (datasourceSetting.type === "table") {
return {
tableId: component[datasourceSetting?.key],
type: "table",
}
+ } else {
+ return component[datasourceSetting?.key]
}
- return null
}
/**
@@ -77,21 +99,37 @@ export const getContextBindings = (rootComponent, componentId) => {
// Extract any components which provide data contexts
const dataProviders = getDataProviderComponents(rootComponent, componentId)
let contextBindings = []
+
+ // Create bindings for each data provider
dataProviders.forEach(component => {
+ const isForm = component._component.endsWith("/form")
const datasource = getDatasourceForProvider(component)
- if (!datasource) {
+ let tableName, schema
+
+ // Forms are an edge case which do not need table schemas
+ if (isForm) {
+ schema = buildFormSchema(component)
+ tableName = "Schema"
+ } else {
+ if (!datasource) {
+ return
+ }
+
+ // Get schema and table for the datasource
+ const info = getSchemaForDatasource(datasource, isForm)
+ schema = info.schema
+ tableName = info.table?.name
+
+ // Add _id and _rev fields for certain types
+ if (datasource.type === "table" || datasource.type === "link") {
+ schema["_id"] = { type: "string" }
+ schema["_rev"] = { type: "string" }
+ }
+ }
+ if (!schema || !tableName) {
return
}
- // Get schema and add _id and _rev fields for certain types
- let { schema, table } = getSchemaForDatasource(datasource)
- if (!schema || !table) {
- return
- }
- if (datasource.type === "table" || datasource.type === "link") {
- schema["_id"] = { type: "string" }
- schema["_rev"] = { type: "string " }
- }
const keys = Object.keys(schema).sort()
// Create bindable properties for each schema field
@@ -110,11 +148,11 @@ export const getContextBindings = (rootComponent, componentId) => {
runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe(
runtimeBoundKey
)}`,
- readableBinding: `${component._instanceName}.${table.name}.${key}`,
+ readableBinding: `${component._instanceName}.${tableName}.${key}`,
+ // Field schema and provider are required to construct relationship
+ // datasource options, based on bindable properties
fieldSchema,
providerId: component._id,
- tableId: datasource.tableId,
- field: key,
})
})
})
@@ -142,44 +180,20 @@ export const getContextBindings = (rootComponent, componentId) => {
type: "context",
runtimeBinding: `user.${runtimeBoundKey}`,
readableBinding: `Current User.${key}`,
+ // Field schema and provider are required to construct relationship
+ // datasource options, based on bindable properties
fieldSchema,
providerId: "user",
- tableId: TableNames.USERS,
- field: key,
})
})
return contextBindings
}
-/**
- * Gets all bindable components. These are form components which allow their
- * values to be bound to.
- */
-export const getComponentBindings = rootComponent => {
- if (!rootComponent) {
- return []
- }
- const componentSelector = component => {
- const type = component._component
- const definition = store.actions.components.getDefinition(type)
- return definition?.bindable
- }
- const components = findAllMatchingComponents(rootComponent, componentSelector)
- return components.map(component => {
- return {
- type: "instance",
- providerId: component._id,
- runtimeBinding: `${makePropSafe(component._id)}`,
- readableBinding: `${component._instanceName}`,
- }
- })
-}
-
/**
* Gets a schema for a datasource object.
*/
-export const getSchemaForDatasource = datasource => {
+export const getSchemaForDatasource = (datasource, isForm = false) => {
let schema, table
if (datasource) {
const { type } = datasource
@@ -193,6 +207,14 @@ export const getSchemaForDatasource = datasource => {
if (table) {
if (type === "view") {
schema = cloneDeep(table.views?.[datasource.name]?.schema)
+ } else if (type === "query" && isForm) {
+ schema = {}
+ const params = table.parameters || []
+ params.forEach(param => {
+ if (param?.name) {
+ schema[param.name] = { ...param, type: "string" }
+ }
+ })
} else {
schema = cloneDeep(table.schema)
}
@@ -201,6 +223,32 @@ export const getSchemaForDatasource = datasource => {
return { schema, table }
}
+/**
+ * Builds a form schema given a form component.
+ * A form schema is a schema of all the fields nested anywhere within a form.
+ */
+const buildFormSchema = component => {
+ let schema = {}
+ if (!component) {
+ return schema
+ }
+ const def = store.actions.components.getDefinition(component._component)
+ const fieldSetting = def?.settings?.find(
+ setting => setting.key === "field" && setting.type.startsWith("field/")
+ )
+ if (fieldSetting && component.field) {
+ const type = fieldSetting.type.split("field/")[1]
+ if (type) {
+ schema[component.field] = { name: component.field, type }
+ }
+ }
+ component._children?.forEach(child => {
+ const childSchema = buildFormSchema(child)
+ schema = { ...schema, ...childSchema }
+ })
+ return schema
+}
+
/**
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
*/
diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index 340e2829aa..27427c6ef0 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -416,7 +416,14 @@ export const getFrontendStore = () => {
if (cut) {
state.componentToPaste = null
} else {
- componentToPaste._id = uuid()
+ const randomizeIds = component => {
+ if (!component) {
+ return
+ }
+ component._id = uuid()
+ component._children?.forEach(randomizeIds)
+ }
+ randomizeIds(componentToPaste)
}
if (mode === "inside") {
diff --git a/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js
index b25562758e..1f30d974ef 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js
@@ -9,5 +9,6 @@ const createScreen = () => {
return new Screen()
.mainType("div")
.component("@budibase/standard-components/container")
+ .instanceName("New Screen")
.json()
}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js
deleted file mode 100644
index a2f2f6df67..0000000000
--- a/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Screen } from "./utils/Screen"
-
-export default {
- name: `New Row (Empty)`,
- create: () => createScreen(),
-}
-
-const createScreen = () => {
- return new Screen()
- .component("@budibase/standard-components/newrow")
- .table("")
- .json()
-}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js
deleted file mode 100644
index 5dbdcf4e69..0000000000
--- a/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Screen } from "./utils/Screen"
-
-export default {
- name: `Row Detail (Empty)`,
- create: () => createScreen(),
-}
-
-const createScreen = () => {
- return new Screen()
- .component("@budibase/standard-components/rowdetail")
- .table("")
- .json()
-}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/index.js b/packages/builder/src/builderStore/store/screenTemplates/index.js
index 7272f3514c..38ae434753 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/index.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/index.js
@@ -1,17 +1,12 @@
import newRowScreen from "./newRowScreen"
import rowDetailScreen from "./rowDetailScreen"
import rowListScreen from "./rowListScreen"
-import emptyNewRowScreen from "./emptyNewRowScreen"
import createFromScratchScreen from "./createFromScratchScreen"
-import emptyRowDetailScreen from "./emptyRowDetailScreen"
const allTemplates = tables => [
- createFromScratchScreen,
...newRowScreen(tables),
...rowDetailScreen(tables),
...rowListScreen(tables),
- emptyNewRowScreen,
- emptyRowDetailScreen,
]
// Allows us to apply common behaviour to all create() functions
@@ -22,8 +17,18 @@ const createTemplateOverride = (frontendState, create) => () => {
return screen
}
-export default (frontendState, tables) =>
- allTemplates(tables).map(template => ({
+export default (frontendState, tables) => {
+ const enrichTemplate = template => ({
...template,
create: createTemplateOverride(frontendState, template.create),
- }))
+ })
+
+ const fromScratch = enrichTemplate(createFromScratchScreen)
+ const tableTemplates = allTemplates(tables).map(enrichTemplate)
+ return [
+ fromScratch,
+ ...tableTemplates.sort((templateA, templateB) => {
+ return templateA.name > templateB.name ? 1 : -1
+ }),
+ ]
+}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js
index 2790a68677..aeac80e7c1 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js
@@ -1,11 +1,12 @@
import sanitizeUrl from "./utils/sanitizeUrl"
-import { Component } from "./utils/Component"
import { Screen } from "./utils/Screen"
+import { Component } from "./utils/Component"
import {
makeBreadcrumbContainer,
- makeMainContainer,
+ makeMainForm,
makeTitleContainer,
makeSaveButton,
+ makeDatasourceFormComponents,
} from "./utils/commonComponents"
export default function(tables) {
@@ -21,29 +22,46 @@ export default function(tables) {
export const newRowUrl = table => sanitizeUrl(`/${table.name}/new/row`)
export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE"
-function generateTitleContainer(table, providerId) {
- return makeTitleContainer("New Row").addChild(
- makeSaveButton(table, providerId)
- )
+function generateTitleContainer(table, formId) {
+ return makeTitleContainer("New Row").addChild(makeSaveButton(table, formId))
}
const createScreen = table => {
const screen = new Screen()
- .component("@budibase/standard-components/newrow")
- .table(table._id)
- .route(newRowUrl(table))
+ .component("@budibase/standard-components/container")
.instanceName(`${table.name} - New`)
- .name("")
+ .route(newRowUrl(table))
- const dataform = new Component(
- "@budibase/standard-components/dataformwide"
- ).instanceName("Form")
+ const form = makeMainForm()
+ .instanceName("Form")
+ .customProps({
+ theme: "spectrum--lightest",
+ size: "spectrum--medium",
+ datasource: {
+ label: table.name,
+ tableId: table._id,
+ type: "table",
+ },
+ })
- const providerId = screen._json.props._id
- const container = makeMainContainer()
+ const fieldGroup = new Component("@budibase/standard-components/fieldgroup")
+ .instanceName("Field Group")
+ .customProps({
+ labelPosition: "left",
+ })
+
+ // Add all form fields from this schema to the field group
+ const datasource = { type: "table", tableId: table._id }
+ makeDatasourceFormComponents(datasource).forEach(component => {
+ fieldGroup.addChild(component)
+ })
+
+ // Add all children to the form
+ const formId = form._json._id
+ form
.addChild(makeBreadcrumbContainer(table.name, "New"))
- .addChild(generateTitleContainer(table, providerId))
- .addChild(dataform)
+ .addChild(generateTitleContainer(table, formId))
+ .addChild(fieldGroup)
- return screen.addChild(container).json()
+ return screen.addChild(form).json()
}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js
index fd54405875..0e48cf307e 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js
@@ -4,20 +4,19 @@ import { Screen } from "./utils/Screen"
import { Component } from "./utils/Component"
import { makePropSafe } from "@budibase/string-templates"
import {
- makeMainContainer,
makeBreadcrumbContainer,
makeTitleContainer,
makeSaveButton,
+ makeMainForm,
+ spectrumColor,
+ makeDatasourceFormComponents,
} from "./utils/commonComponents"
export default function(tables) {
return tables.map(table => {
- const heading = table.primaryDisplay
- ? `{{ data.${makePropSafe(table.primaryDisplay)} }}`
- : null
return {
name: `${table.name} - Detail`,
- create: () => createScreen(table, heading),
+ create: () => createScreen(table),
id: ROW_DETAIL_TEMPLATE,
}
})
@@ -26,9 +25,9 @@ export default function(tables) {
export const ROW_DETAIL_TEMPLATE = "ROW_DETAIL_TEMPLATE"
export const rowDetailUrl = table => sanitizeUrl(`/${table.name}/:id`)
-function generateTitleContainer(table, title, providerId) {
+function generateTitleContainer(table, title, formId) {
// have to override style for this, its missing margin
- const saveButton = makeSaveButton(table, providerId).normalStyle({
+ const saveButton = makeSaveButton(table, formId).normalStyle({
background: "#000000",
"border-width": "0",
"border-style": "None",
@@ -54,6 +53,7 @@ function generateTitleContainer(table, title, providerId) {
background: "transparent",
color: "#4285f4",
})
+ .customStyle(spectrumColor(700))
.text("Delete")
.customProps({
className: "",
@@ -61,8 +61,9 @@ function generateTitleContainer(table, title, providerId) {
onClick: [
{
parameters: {
- rowId: `{{ ${makePropSafe(providerId)}._id }}`,
- revId: `{{ ${makePropSafe(providerId)}._rev }}`,
+ providerId: formId,
+ rowId: `{{ ${makePropSafe(formId)}._id }}`,
+ revId: `{{ ${makePropSafe(formId)}._rev }}`,
tableId: table._id,
},
"##eventHandlerType": "Delete Row",
@@ -82,23 +83,47 @@ function generateTitleContainer(table, title, providerId) {
.addChild(saveButton)
}
-const createScreen = (table, heading) => {
+const createScreen = table => {
const screen = new Screen()
.component("@budibase/standard-components/rowdetail")
.table(table._id)
.instanceName(`${table.name} - Detail`)
.route(rowDetailUrl(table))
- .name("")
- const dataform = new Component(
- "@budibase/standard-components/dataformwide"
- ).instanceName("Form")
+ const form = makeMainForm()
+ .instanceName("Form")
+ .customProps({
+ theme: "spectrum--lightest",
+ size: "spectrum--medium",
+ datasource: {
+ label: table.name,
+ tableId: table._id,
+ type: "table",
+ },
+ })
- const providerId = screen._json.props._id
- const container = makeMainContainer()
+ const fieldGroup = new Component("@budibase/standard-components/fieldgroup")
+ .instanceName("Field Group")
+ .customProps({
+ labelPosition: "left",
+ })
+
+ // Add all form fields from this schema to the field group
+ const datasource = { type: "table", tableId: table._id }
+ makeDatasourceFormComponents(datasource).forEach(component => {
+ fieldGroup.addChild(component)
+ })
+
+ // Add all children to the form
+ const formId = form._json._id
+ const rowDetailId = screen._json.props._id
+ const heading = table.primaryDisplay
+ ? `{{ ${makePropSafe(rowDetailId)}.${makePropSafe(table.primaryDisplay)} }}`
+ : null
+ form
.addChild(makeBreadcrumbContainer(table.name, heading || "Edit"))
- .addChild(generateTitleContainer(table, heading || "Edit Row", providerId))
- .addChild(dataform)
+ .addChild(generateTitleContainer(table, heading || "Edit Row", formId))
+ .addChild(fieldGroup)
- return screen.addChild(container).json()
+ return screen.addChild(form).json()
}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js
index a74ea526f7..182736a1d5 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js
@@ -14,17 +14,11 @@ export class Component extends BaseStructure {
active: {},
selected: {},
},
- type: "",
_instanceName: "",
_children: [],
}
}
- type(type) {
- this._json.type = type
- return this
- }
-
normalStyle(styling) {
this._json._styles.normal = styling
return this
@@ -35,14 +29,25 @@ export class Component extends BaseStructure {
return this
}
- text(text) {
- this._json.text = text
+ customStyle(styling) {
+ this._json._styles.custom = styling
return this
}
- // TODO: do we need this
instanceName(name) {
this._json._instanceName = name
return this
}
+
+ // Shorthand for custom props "type"
+ type(type) {
+ this._json.type = type
+ return this
+ }
+
+ // Shorthand for custom props "text"
+ text(text) {
+ this._json.text = text
+ return this
+ }
}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js
index a00f66f828..4c127fbe0b 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js
@@ -1,5 +1,15 @@
import { Component } from "./Component"
import { rowListUrl } from "../rowListScreen"
+import { getSchemaForDatasource } from "../../../dataBinding"
+
+export function spectrumColor(number) {
+ // Acorn throws a parsing error in this file if the word g-l-o-b-a-l is found
+ // (without dashes - I can't even type it in a comment).
+ // God knows why. It seems to think optional chaining further down the
+ // file is invalid if the word g-l-o-b-a-l is found - hence the reason this
+ // statement is split into parts.
+ return "color: var(--spectrum-glo" + `bal-color-gray-${number});`
+}
export function makeLinkComponent(tableName) {
return new Component("@budibase/standard-components/link")
@@ -10,6 +20,7 @@ export function makeLinkComponent(tableName) {
.hoverStyle({
color: "#4285f4",
})
+ .customStyle(spectrumColor(700))
.text(tableName)
.customProps({
url: `/${tableName.toLowerCase()}`,
@@ -22,13 +33,12 @@ export function makeLinkComponent(tableName) {
})
}
-export function makeMainContainer() {
- return new Component("@budibase/standard-components/container")
+export function makeMainForm() {
+ return new Component("@budibase/standard-components/form")
.type("div")
.normalStyle({
width: "700px",
padding: "0px",
- background: "white",
"border-radius": "0.5rem",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
margin: "auto",
@@ -39,7 +49,7 @@ export function makeMainContainer() {
"padding-left": "48px",
"margin-bottom": "20px",
})
- .instanceName("Container")
+ .instanceName("Form")
}
export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
@@ -51,6 +61,7 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
"margin-right": "4px",
"margin-left": "4px",
})
+ .customStyle(spectrumColor(700))
.text(">")
.instanceName("Arrow")
@@ -63,6 +74,7 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
const identifierText = new Component("@budibase/standard-components/text")
.type("none")
.normalStyle(textStyling)
+ .customStyle(spectrumColor(700))
.text(text)
.instanceName("Identifier")
@@ -78,7 +90,7 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
.addChild(identifierText)
}
-export function makeSaveButton(table, providerId) {
+export function makeSaveButton(table, formId) {
return new Component("@budibase/standard-components/button")
.normalStyle({
background: "#000000",
@@ -99,8 +111,14 @@ export function makeSaveButton(table, providerId) {
disabled: false,
onClick: [
{
+ "##eventHandlerType": "Validate Form",
parameters: {
- providerId,
+ componentId: formId,
+ },
+ },
+ {
+ parameters: {
+ providerId: formId,
},
"##eventHandlerType": "Save Row",
},
@@ -125,6 +143,7 @@ export function makeTitleContainer(title) {
"margin-left": "0px",
flex: "1 1 auto",
})
+ .customStyle(spectrumColor(900))
.type("h3")
.instanceName("Title")
.text(title)
@@ -142,3 +161,44 @@ export function makeTitleContainer(title) {
.instanceName("Title Container")
.addChild(heading)
}
+
+const fieldTypeToComponentMap = {
+ string: "stringfield",
+ number: "numberfield",
+ options: "optionsfield",
+ boolean: "booleanfield",
+ longform: "longformfield",
+ datetime: "datetimefield",
+ attachment: "attachmentfield",
+ link: "relationshipfield",
+}
+
+export function makeDatasourceFormComponents(datasource) {
+ const { schema } = getSchemaForDatasource(datasource, true)
+ let components = []
+ let fields = Object.keys(schema || {})
+ fields.forEach(field => {
+ const fieldSchema = schema[field]
+ const fieldType =
+ typeof fieldSchema === "object" ? fieldSchema.type : fieldSchema
+ const componentType = fieldTypeToComponentMap[fieldType]
+ const fullComponentType = `@budibase/standard-components/${componentType}`
+ if (componentType) {
+ const component = new Component(fullComponentType)
+ .instanceName(field)
+ .customProps({
+ field,
+ label: field,
+ placeholder: field,
+ })
+ if (fieldType === "options") {
+ component.customProps({ placeholder: "Choose an option " })
+ }
+ if (fieldType === "boolean") {
+ component.customProps({ text: field, label: "" })
+ }
+ components.push(component)
+ }
+ })
+ return components
+}
diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js
index 00f5a209a3..6d0f0beab0 100644
--- a/packages/builder/src/builderStore/storeUtils.js
+++ b/packages/builder/src/builderStore/storeUtils.js
@@ -59,8 +59,8 @@ export const findComponentPath = (rootComponent, id, path = []) => {
}
/**
- * Recurses through the component tree and finds all components of a certain
- * type.
+ * Recurses through the component tree and finds all components which match
+ * a certain selector
*/
export const findAllMatchingComponents = (rootComponent, selector) => {
if (!rootComponent || !selector) {
@@ -81,6 +81,26 @@ export const findAllMatchingComponents = (rootComponent, selector) => {
return components.reverse()
}
+/**
+ * Finds the closes parent component which matches certain criteria
+ */
+export const findClosestMatchingComponent = (
+ rootComponent,
+ componentId,
+ selector
+) => {
+ if (!selector) {
+ return null
+ }
+ const componentPath = findComponentPath(rootComponent, componentId).reverse()
+ for (let component of componentPath) {
+ if (selector(component)) {
+ return component
+ }
+ }
+ return null
+}
+
/**
* Recurses through a component tree evaluating a matching function against
* components until a match is found
diff --git a/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte b/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte
index 3ee6c0fd31..e6818518ad 100644
--- a/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte
@@ -2,7 +2,6 @@
import groupBy from "lodash/fp/groupBy"
import {
TextArea,
- Label,
Input,
Heading,
Body,
diff --git a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte
index bddb66e4c9..3390b95288 100644
--- a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte
+++ b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte
@@ -36,7 +36,9 @@
{:else if type === 'boolean'}