diff --git a/packages/builder/src/components/userInterface/TableViewSelect.svelte b/packages/builder/src/components/userInterface/TableViewSelect.svelte
index 4fdb477cbf..0b8e26abe8 100644
--- a/packages/builder/src/components/userInterface/TableViewSelect.svelte
+++ b/packages/builder/src/components/userInterface/TableViewSelect.svelte
@@ -40,13 +40,16 @@
$: links = bindableProperties
.filter(x => x.fieldSchema?.type === "link")
- .map(property => ({
- label: property.readableBinding,
- fieldName: property.fieldSchema.name,
- name: `all_${property.fieldSchema.tableId}`,
- tableId: property.fieldSchema.tableId,
- type: "link",
- }))
+ .map(property => {
+ return {
+ providerId: property.instance._id,
+ label: property.readableBinding,
+ fieldName: property.fieldSchema.name,
+ name: `all_${property.fieldSchema.tableId}`,
+ tableId: property.fieldSchema.tableId,
+ type: "link",
+ }
+ })
{
} else if (type === "view") {
rows = await fetchViewData(datasource)
} else if (type === "link") {
+ const row = dataContext[datasource.providerId]
rows = await fetchRelationshipData({
- rowId: dataContext?._id,
- tableId: dataContext?.tableId,
+ rowId: row?._id,
+ tableId: row?.tableId,
fieldName,
})
}
diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte
index f4af80ab83..d9b5f79538 100644
--- a/packages/client/src/components/ClientApp.svelte
+++ b/packages/client/src/components/ClientApp.svelte
@@ -1,11 +1,13 @@
{#if constructor}
diff --git a/packages/client/src/components/DataProvider.svelte b/packages/client/src/components/DataProvider.svelte
index 4ee04d722c..f33f01d425 100644
--- a/packages/client/src/components/DataProvider.svelte
+++ b/packages/client/src/components/DataProvider.svelte
@@ -4,15 +4,11 @@
export let row
- // Get current contexts
+ // Clone and create new data context for this component tree
const data = getContext("data")
const component = getContext("component")
-
- // Clone current context to this context
const newData = createDataStore($data)
setContext("data", newData)
-
- // Add additional layer to context
$: newData.actions.addContext(row, $component.id)
diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js
index 3a5a3f80ac..a0b8aa6719 100644
--- a/packages/client/src/sdk.js
+++ b/packages/client/src/sdk.js
@@ -1,6 +1,7 @@
import * as API from "./api"
import { authStore, routeStore, screenStore, bindingStore } from "./store"
-import { styleable, getAppId } from "./utils"
+import { styleable } from "./utils/styleable"
+import { getAppId } from "./utils/getAppId"
import { link as linkable } from "svelte-spa-router"
import DataProvider from "./components/DataProvider.svelte"
diff --git a/packages/client/src/store/auth.js b/packages/client/src/store/auth.js
index ac616e9240..a7c91a0972 100644
--- a/packages/client/src/store/auth.js
+++ b/packages/client/src/store/auth.js
@@ -1,5 +1,5 @@
import * as API from "../api"
-import { getAppId } from "../utils"
+import { getAppId } from "../utils/getAppId"
import { writable } from "svelte/store"
const createAuthStore = () => {
diff --git a/packages/client/src/store/data.js b/packages/client/src/store/data.js
index 9cd44eebb4..5ff2b9b631 100644
--- a/packages/client/src/store/data.js
+++ b/packages/client/src/store/data.js
@@ -1,20 +1,16 @@
import { writable } from "svelte/store"
import { cloneDeep } from "lodash/fp"
-const initialValue = {
- data: null,
-}
-
export const createDataStore = existingContext => {
- const initial = existingContext ? cloneDeep(existingContext) : initialValue
- const store = writable(initial)
+ const store = writable({ ...existingContext })
// Adds a context layer to the data context tree
const addContext = (row, componentId) => {
store.update(state => {
if (componentId) {
state[componentId] = row
- state.data = row
+ state[`${componentId}_draft`] = cloneDeep(row)
+ state.closestComponentId = componentId
}
return state
})
@@ -22,6 +18,9 @@ export const createDataStore = existingContext => {
return {
subscribe: store.subscribe,
+ update: store.update,
actions: { addContext },
}
}
+
+export const dataStore = createDataStore()
diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js
index 3730f39ee0..58e51b6fc1 100644
--- a/packages/client/src/store/index.js
+++ b/packages/client/src/store/index.js
@@ -5,4 +5,4 @@ export { builderStore } from "./builder"
export { bindingStore } from "./binding"
// Data stores are layered and duplicated, so it is not a singleton
-export { createDataStore } from "./data"
+export { createDataStore, dataStore } from "./data"
diff --git a/packages/client/src/store/screens.js b/packages/client/src/store/screens.js
index f19736756a..2d977ec025 100644
--- a/packages/client/src/store/screens.js
+++ b/packages/client/src/store/screens.js
@@ -2,7 +2,7 @@ import { writable, derived } from "svelte/store"
import { routeStore } from "./routes"
import { builderStore } from "./builder"
import * as API from "../api"
-import { getAppId } from "../utils"
+import { getAppId } from "../utils/getAppId"
const createScreenStore = () => {
const config = writable({
diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js
new file mode 100644
index 0000000000..2968525188
--- /dev/null
+++ b/packages/client/src/utils/buttonActions.js
@@ -0,0 +1,45 @@
+import { enrichDataBinding } from "./enrichDataBinding"
+import { routeStore } from "../store"
+import { saveRow, deleteRow } from "../api"
+
+const saveRowHandler = async (action, context) => {
+ let draft = context[`${action.parameters.contextPath}_draft`]
+ if (action.parameters.fields) {
+ Object.entries(action.parameters.fields).forEach(([key, entry]) => {
+ draft[key] = enrichDataBinding(entry.value, context)
+ })
+ }
+ await saveRow(draft)
+}
+
+const deleteRowHandler = async (action, context) => {
+ const { tableId, revId, rowId } = action.parameters
+ await deleteRow({
+ tableId: enrichDataBinding(tableId, context),
+ rowId: enrichDataBinding(rowId, context),
+ revId: enrichDataBinding(revId, context),
+ })
+}
+
+const navigationHandler = action => {
+ routeStore.actions.navigate(action.parameters.url)
+}
+
+const handlerMap = {
+ ["Save Row"]: saveRowHandler,
+ ["Delete Row"]: deleteRowHandler,
+ ["Navigate To"]: navigationHandler,
+}
+
+/**
+ * Parses an array of actions and returns a function which will execute the
+ * actions in the current context.
+ */
+export const enrichButtonActions = (actions, context) => {
+ const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
+ return async () => {
+ for (let i = 0; i < handlers.length; i++) {
+ await handlers[i](actions[i], context)
+ }
+ }
+}
diff --git a/packages/client/src/utils/componentProps.js b/packages/client/src/utils/componentProps.js
new file mode 100644
index 0000000000..be65ad2bfe
--- /dev/null
+++ b/packages/client/src/utils/componentProps.js
@@ -0,0 +1,35 @@
+import { enrichDataBindings } from "./enrichDataBinding"
+import { enrichButtonActions } from "./buttonActions"
+
+/**
+ * Enriches component props.
+ * Data bindings are enriched, and button actions are enriched.
+ */
+export const enrichProps = (props, dataContexts, dataBindings) => {
+ // Exclude all private props that start with an underscore
+ let validProps = {}
+ Object.entries(props)
+ .filter(([name]) => !name.startsWith("_"))
+ .forEach(([key, value]) => {
+ validProps[key] = value
+ })
+
+ // Create context of all bindings and data contexts
+ // Duplicate the closest context as "data" which the builder requires
+ const context = {
+ ...dataContexts,
+ ...dataBindings,
+ data: dataContexts[dataContexts.closestComponentId],
+ data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
+ }
+
+ // Enrich all data bindings in top level props
+ let enrichedProps = enrichDataBindings(validProps, context)
+
+ // Enrich button actions if they exist
+ if (props._component.endsWith("/button") && enrichedProps.onClick) {
+ enrichedProps.onClick = enrichButtonActions(enrichedProps.onClick, context)
+ }
+
+ return enrichedProps
+}
diff --git a/packages/client/src/utils/enrichDataBinding.js b/packages/client/src/utils/enrichDataBinding.js
index 93d51daf10..5de6b31a89 100644
--- a/packages/client/src/utils/enrichDataBinding.js
+++ b/packages/client/src/utils/enrichDataBinding.js
@@ -33,3 +33,14 @@ export const enrichDataBinding = (input, context) => {
}
return mustache.render(input, context)
}
+
+/**
+ * Enriches each prop in a props object
+ */
+export const enrichDataBindings = (props, context) => {
+ let enrichedProps = {}
+ Object.entries(props).forEach(([key, value]) => {
+ enrichedProps[key] = enrichDataBinding(value, context)
+ })
+ return enrichedProps
+}
diff --git a/packages/client/src/utils/index.js b/packages/client/src/utils/index.js
deleted file mode 100644
index 2ccba5dcde..0000000000
--- a/packages/client/src/utils/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { getAppId } from "./getAppId"
-export { styleable } from "./styleable"
-export { enrichDataBinding } from "./enrichDataBinding"
diff --git a/packages/standard-components/src/Button.svelte b/packages/standard-components/src/Button.svelte
index 1d64c5e8bd..e2a98bb74d 100644
--- a/packages/standard-components/src/Button.svelte
+++ b/packages/standard-components/src/Button.svelte
@@ -7,12 +7,14 @@
export let className = "default"
export let disabled = false
export let text
+ export let onClick
diff --git a/packages/standard-components/src/Form.svelte b/packages/standard-components/src/Form.svelte
index 8639fbde1a..0a64eeb490 100644
--- a/packages/standard-components/src/Form.svelte
+++ b/packages/standard-components/src/Form.svelte
@@ -5,8 +5,9 @@
import LinkedRowSelector from "./LinkedRowSelector.svelte"
import { capitalise } from "./helpers"
- const { styleable, screenStore, API } = getContext("sdk")
+ const { styleable, API } = getContext("sdk")
const component = getContext("component")
+ const data = getContext("data")
export let wide = false
@@ -14,14 +15,17 @@
let schema
let fields = []
- $: getContextDetails($component.dataContext)
+ // Fetch info about the closest data context
+ $: getFormData($data[$data.closestComponentId])
- const getContextDetails = async dataContext => {
- if (dataContext) {
- row = dataContext
- const tableDefinition = await API.fetchTableDefinition(row.tableId)
+ const getFormData = async context => {
+ if (context) {
+ const tableDefinition = await API.fetchTableDefinition(context.tableId)
schema = tableDefinition.schema
fields = Object.keys(schema)
+
+ // Use the draft version for editing
+ row = $data[`${$data.closestComponentId}_draft`]
}
}
diff --git a/packages/standard-components/src/List.svelte b/packages/standard-components/src/List.svelte
index a305bff04d..226287490f 100644
--- a/packages/standard-components/src/List.svelte
+++ b/packages/standard-components/src/List.svelte
@@ -4,6 +4,7 @@
const { API, styleable, DataProvider } = getContext("sdk")
const component = getContext("component")
+ const data = getContext("data")
export let datasource = []
@@ -11,7 +12,7 @@
onMount(async () => {
if (!isEmpty(datasource)) {
- rows = await API.fetchDatasource(datasource, $component.dataContext)
+ rows = await API.fetchDatasource(datasource, $data)
}
})
diff --git a/packages/standard-components/src/charts/BarChart.svelte b/packages/standard-components/src/charts/BarChart.svelte
index a4937d70df..872f7e624d 100644
--- a/packages/standard-components/src/charts/BarChart.svelte
+++ b/packages/standard-components/src/charts/BarChart.svelte
@@ -5,7 +5,6 @@
import { isEmpty } from "lodash/fp"
const { API } = getContext("sdk")
- const component = getContext("component")
export let title
export let datasource
@@ -35,7 +34,7 @@
// Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
- const result = await API.fetchDatasource(datasource, $component.dataContext)
+ const result = await API.fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result
diff --git a/packages/standard-components/src/charts/CandleStickChart.svelte b/packages/standard-components/src/charts/CandleStickChart.svelte
index cb5b6d2607..c5ca0ebd49 100644
--- a/packages/standard-components/src/charts/CandleStickChart.svelte
+++ b/packages/standard-components/src/charts/CandleStickChart.svelte
@@ -5,7 +5,6 @@
import { isEmpty } from "lodash/fp"
const { API } = getContext("sdk")
- const component = getContext("component")
export let title
export let datasource
@@ -33,7 +32,7 @@
// Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
- const result = await API.fetchDatasource(datasource, $component.dataContext)
+ const result = await API.fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result
diff --git a/packages/standard-components/src/charts/LineChart.svelte b/packages/standard-components/src/charts/LineChart.svelte
index 5eff91a324..cdfb4f5fbf 100644
--- a/packages/standard-components/src/charts/LineChart.svelte
+++ b/packages/standard-components/src/charts/LineChart.svelte
@@ -5,7 +5,6 @@
import { isEmpty } from "lodash/fp"
const { API } = getContext("sdk")
- const component = getContext("component")
// Common props
export let title
@@ -41,7 +40,7 @@
// Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
- const result = await API.fetchDatasource(datasource, $component.dataContext)
+ const result = await API.fetchDatasource(datasource)
const reducer = row => (valid, column) => valid && row[column] != null
const hasAllColumns = row => allCols.reduce(reducer(row), true)
const data = result
diff --git a/packages/standard-components/src/charts/PieChart.svelte b/packages/standard-components/src/charts/PieChart.svelte
index 584b88bbec..7b8df25b01 100644
--- a/packages/standard-components/src/charts/PieChart.svelte
+++ b/packages/standard-components/src/charts/PieChart.svelte
@@ -5,7 +5,6 @@
import { isEmpty } from "lodash/fp"
const { API } = getContext("sdk")
- const component = getContext("component")
export let title
export let datasource
@@ -31,7 +30,7 @@
// Fetch, filter and sort data
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
- const result = await API.fetchDatasource(datasource, $component.dataContext)
+ const result = await API.fetchDatasource(datasource)
const data = result
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
.slice(0, 20)
diff --git a/packages/standard-components/src/grid/Component.svelte b/packages/standard-components/src/grid/Component.svelte
index 3911501735..69f7954898 100644
--- a/packages/standard-components/src/grid/Component.svelte
+++ b/packages/standard-components/src/grid/Component.svelte
@@ -58,7 +58,7 @@
onMount(async () => {
if (!isEmpty(datasource)) {
- data = await API.fetchDatasource(datasource, $component.dataContext)
+ data = await API.fetchDatasource(datasource)
let schema
// Get schema for datasource