From 907c0fcfda76e7ae7e5408863248c66e1489a524 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 25 Nov 2020 09:50:51 +0000 Subject: [PATCH] Add button actions, simplify contexts and tidy up --- .../userInterface/TableViewSelect.svelte | 17 +++-- packages/client/src/api/api.js | 2 +- packages/client/src/api/datasources.js | 5 +- .../client/src/components/ClientApp.svelte | 4 +- .../client/src/components/Component.svelte | 66 +++++-------------- .../client/src/components/DataProvider.svelte | 6 +- packages/client/src/sdk.js | 3 +- packages/client/src/store/auth.js | 2 +- packages/client/src/store/data.js | 13 ++-- packages/client/src/store/index.js | 2 +- packages/client/src/store/screens.js | 2 +- packages/client/src/utils/buttonActions.js | 45 +++++++++++++ packages/client/src/utils/componentProps.js | 35 ++++++++++ .../client/src/utils/enrichDataBinding.js | 11 ++++ packages/client/src/utils/index.js | 3 - .../standard-components/src/Button.svelte | 4 +- packages/standard-components/src/Form.svelte | 16 +++-- packages/standard-components/src/List.svelte | 3 +- .../src/charts/BarChart.svelte | 3 +- .../src/charts/CandleStickChart.svelte | 3 +- .../src/charts/LineChart.svelte | 3 +- .../src/charts/PieChart.svelte | 3 +- .../src/grid/Component.svelte | 2 +- 23 files changed, 156 insertions(+), 97 deletions(-) create mode 100644 packages/client/src/utils/buttonActions.js create mode 100644 packages/client/src/utils/componentProps.js delete mode 100644 packages/client/src/utils/index.js 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