Add support for full depth data binding

This commit is contained in:
Andrew Kingston 2020-11-20 09:50:10 +00:00
parent 7fb26408cf
commit 1b0fa94dff
44 changed files with 169 additions and 138 deletions

View File

@ -6,7 +6,7 @@ import { enrichRows } from "./rows"
/**
* Fetches all rows for a particular Budibase data source.
*/
export const fetchDatasource = async (datasource, context) => {
export const fetchDatasource = async (datasource, dataContext) => {
if (!datasource || !datasource.type) {
return []
}
@ -20,9 +20,9 @@ export const fetchDatasource = async (datasource, context) => {
rows = await fetchViewData(datasource)
} else if (type === "link") {
rows = await fetchRelationshipData({
tableId: context?.row?.tableId,
rowId: dataContext?.data?._id,
tableId: dataContext?.data?.tableId,
fieldName,
rowId: context?.row?._id,
})
}

View File

@ -2,10 +2,11 @@
import { setContext, onMount } from "svelte"
import Component from "./Component.svelte"
import SDK from "../sdk"
import { routeStore, screenStore } from "../store"
import { routeStore, screenStore, createDataContextStore } from "../store"
// Provide SDK for components
setContext("app", SDK)
// Provide contexts
setContext("sdk", SDK)
setContext("data", createDataContextStore())
let loaded = false

View File

@ -1,21 +1,10 @@
<script>
import { getContext } from "svelte"
import { getContext, setContext } from "svelte"
import * as ComponentLibrary from "@budibase/standard-components"
import Router from "./Router.svelte"
import enrichDataBinding from "../utils/enrichDataBinding"
const dataProviderStore = getContext("data")
export let definition = {}
$: contextRow = dataProviderStore ? $dataProviderStore.row : undefined
$: componentProps = extractValidProps(definition, contextRow)
$: children = definition._children
$: componentName = extractComponentName(definition._component)
$: constructor = getComponentConstructor(componentName)
$: id = `${componentName}-${definition._id}`
$: styles = { ...definition._styles, id }
// Extracts the actual component name from the library name
const extractComponentName = name => {
const split = name?.split("/")
@ -23,13 +12,12 @@
}
// Extracts valid props to pass to the real svelte component
const extractValidProps = (component, row) => {
const extractValidProps = component => {
let props = {}
const enrich = value => enrichDataBinding(value, { data: row })
Object.entries(component)
.filter(([name]) => !name.startsWith("_"))
.forEach(([key, value]) => {
props[key] = row === undefined ? value : enrich(value)
props[key] = value
})
return props
}
@ -39,11 +27,22 @@
return name === "screenslot" ? Router : ComponentLibrary[componentName]
}
// Extract component definition info
const componentName = extractComponentName(definition._component)
const constructor = getComponentConstructor(componentName)
const id = `${componentName}-${definition._id}`
const componentProps = extractValidProps(definition)
const dataContext = getContext("data")
const enrichedProps = dataContext.actions.enrichDataBindings(componentProps)
const children = definition._children
// Set style context to be consumed by component
setContext("style", { ...definition._styles, id })
$: console.log("Rendering: " + componentName)
</script>
{#if constructor}
<svelte:component this={constructor} {...componentProps} {styles}>
<svelte:component this={constructor} {...enrichedProps}>
{#if children && children.length}
{#each children as child}
<svelte:self definition={child} />

View File

@ -0,0 +1,24 @@
<script>
import { onMount, getContext, setContext } from "svelte"
import { createDataContextStore } from "../store"
export let row
// Get current contexts
const dataContext = getContext("data")
// Clone current context to this context
const newDataContext = createDataContextStore($dataContext)
setContext("data", newDataContext)
// Add additional layer to context
let loaded = false
onMount(() => {
newDataContext.actions.addContext(row)
loaded = true
})
</script>
{#if loaded}
<slot />
{/if}

View File

@ -1,11 +1,12 @@
<script>
import { onMount } from "svelte"
import { getContext } from "svelte"
import Router from "svelte-spa-router"
import { routeStore, screenStore } from "../store"
import Screen from "./Screen.svelte"
import { styleable } from "../utils"
export let styles
const { styleable } = getContext("sdk")
const styles = getContext("style")
$: routerConfig = getRouterConfig($routeStore.routes)
const getRouterConfig = routes => {

View File

@ -2,6 +2,7 @@ import * as API from "./api"
import { authStore, routeStore, screenStore } from "./store"
import { styleable, getAppId } from "./utils"
import { link as linkable } from "svelte-spa-router"
import DataProvider from "./components/DataProvider.svelte"
export default {
API,
@ -11,4 +12,5 @@ export default {
styleable,
linkable,
getAppId,
DataProvider,
}

View File

@ -1,7 +1,6 @@
import * as API from "../api"
import { getAppId } from "../utils"
import { writable } from "svelte/store"
import { loc } from "svelte-spa-router"
const createAuthStore = () => {
const store = writable("")

View File

@ -0,0 +1,43 @@
import { writable, get } from "svelte/store"
import { enrichDataBinding } from "../utils"
import { cloneDeep } from "lodash/fp"
const initialValue = {
parent: null,
data: null,
}
export const createDataContextStore = existingContext => {
const initial = existingContext ? cloneDeep(existingContext) : initialValue
const store = writable(initial)
// Adds a context layer to the data context tree
const addContext = row => {
store.update(state => {
if (state.data) {
state.parent = {
parent: state.parent,
data: state.data,
}
}
state.data = row
return state
})
}
// Enriches props by running mustache and filling in any data bindings present
// in the prop values
const enrichDataBindings = props => {
const state = get(store)
let enrichedProps = {}
Object.entries(props).forEach(([key, value]) => {
enrichedProps[key] = enrichDataBinding(value, state)
})
return enrichedProps
}
return {
subscribe: store.subscribe,
actions: { addContext, enrichDataBindings },
}
}

View File

@ -1,3 +1,4 @@
export { authStore } from "./auth"
export { routeStore } from "./routes"
export { screenStore } from "./screens"
export { createDataContextStore } from "./dataContext"

View File

@ -19,7 +19,7 @@ const looksLikeMustache = /{{.*}}/
/**
* Enriches a given input with a row from the database.
*/
export default (input, context) => {
export const enrichDataBinding = (input, context) => {
// Only accept string inputs
if (!input || typeof input !== "string") {
return input
@ -28,6 +28,8 @@ export default (input, context) => {
if (!looksLikeMustache.test(input)) {
return input
}
console.log("====================================")
console.log(input)
console.log(context)
return mustache.render(input, context)
}

View File

@ -1,2 +1,3 @@
export { getAppId } from "./getAppId"
export { styleable } from "./styleable"
export { enrichDataBinding } from "./enrichDataBinding"

View File

@ -1,12 +1,12 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let className = "default"
export let disabled = false
export let text
export let styles
</script>
<button class="default" disabled={disabled || false} use:styleable={styles}>

View File

@ -2,7 +2,8 @@
import { getContext } from "svelte"
import { cssVars } from "./helpers"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export const className = ""
export let imageUrl = ""
@ -14,7 +15,6 @@
export let linkHoverColor
export let imageHeight
export let cardWidth
export let styles
$: cssVariables = {
color,

View File

@ -2,7 +2,8 @@
import { getContext } from "svelte"
import { cssVars } from "./helpers"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export const className = ""
export let imageUrl = ""
@ -16,7 +17,6 @@
export let cardWidth
export let imageWidth
export let imageHeight
export let styles
$: cssVariables = {
color,

View File

@ -1,11 +1,11 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let className = ""
export let type = "div"
export let styles
</script>
{#if type === 'div'}

View File

@ -1,7 +1,5 @@
<script>
import Form from "./Form.svelte"
export let styles
</script>
<Form {styles} wide={false} />
<Form wide={false} />

View File

@ -1,7 +1,5 @@
<script>
import Form from "./Form.svelte"
export let styles
</script>
<Form {styles} wide />
<Form wide />

View File

@ -1,19 +0,0 @@
<script>
import { setContext, onMount } from "svelte"
import { createDataProviderStore } from "./stores/dataProvider"
const dataProviderStore = createDataProviderStore()
setContext("data", dataProviderStore)
export let row
let loaded = false
onMount(async () => {
await dataProviderStore.actions.setRow(row)
loaded = true
})
</script>
{#if loaded}
<slot />
{/if}

View File

@ -2,11 +2,11 @@
import { DatePicker } from "@budibase/bbui"
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let placeholder
export let value
export let styles
function handleChange(event) {
const [fullDate] = event.detail

View File

@ -1,10 +1,10 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let embed
export let styles
</script>
<div use:styleable={styles}>

View File

@ -5,15 +5,26 @@
import LinkedRowSelector from "./LinkedRowSelector.svelte"
import { capitalise } from "./helpers"
const { styleable, screenStore } = getContext("app")
const dataProviderStore = getContext("data")
const { styleable, screenStore, API } = getContext("sdk")
const dataContextStore = getContext("data")
const styles = getContext("style")
export let wide = false
export let styles
$: row = $dataProviderStore?.row
$: schema = $dataProviderStore?.table && $dataProviderStore.table.schema
$: fields = schema ? Object.keys(schema) : []
let row
let schema
let fields = []
$: getContextDetails($dataContextStore)
const getContextDetails = async dataContext => {
row = dataContext?.data
if (row) {
const tableDefinition = await API.fetchTableDefinition(row.tableId)
schema = tableDefinition.schema
fields = Object.keys(schema)
}
}
</script>
<div class="form-content" use:styleable={styles}>

View File

@ -1,12 +1,12 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let className = ""
export let type
export let text = ""
export let styles
</script>
{#if type === 'h1'}

View File

@ -2,12 +2,12 @@
import "@fortawesome/fontawesome-free/js/all.js"
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let icon = ""
export let size = "fa-lg"
export let color = "#000"
export let styles
</script>
<i

View File

@ -1,14 +1,14 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let className = ""
export let url = ""
export let description = ""
export let height
export let width
export let styles
</script>
<img

View File

@ -1,12 +1,12 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let value = ""
export let className = ""
export let type = "text"
export let styles
const onchange = ev => {
value = ev.target.value

View File

@ -1,12 +1,12 @@
<script>
import { getContext } from "svelte"
const { linkable, styleable } = getContext("app")
const { linkable, styleable } = getContext("sdk")
const styles = getContext("style")
export let url = ""
export let text = ""
export let openInNewTab = false
export let styles
$: target = openInNewTab ? "_blank" : "_self"
</script>

View File

@ -3,7 +3,7 @@
import { capitalise } from "./helpers"
import { getContext } from "svelte"
const { API } = getContext("app")
const { API } = getContext("sdk")
export let schema = {}
export let linkedRows = []

View File

@ -1,19 +1,18 @@
<script>
import { getContext, onMount } from "svelte"
import { isEmpty } from "lodash/fp"
import DataProvider from "./DataProvider.svelte"
const { API, styleable } = getContext("app")
const dataContext = getContext("data")
const { API, styleable, DataProvider } = getContext("sdk")
const dataContextStore = getContext("data")
const styles = getContext("style")
export let datasource = []
export let styles
let rows = []
onMount(async () => {
if (!isEmpty(datasource)) {
rows = await API.fetchDatasource(datasource, $dataContext)
rows = await API.fetchDatasource(datasource, $dataContextStore)
}
})
</script>

View File

@ -1,14 +1,14 @@
<script>
import { getContext } from "svelte"
const { authStore, styleable } = getContext("app")
const { authStore, styleable } = getContext("sdk")
const styles = getContext("style")
export let buttonText = "Log In"
export let logo = ""
export let title = ""
export let buttonClass = ""
export let inputClass = ""
export let styles
let username = ""
let password = ""

View File

@ -1,11 +1,11 @@
<script>
import { getContext } from "svelte"
const { authStore, linkable, styleable } = getContext("app")
const { authStore, linkable, styleable } = getContext("sdk")
const styles = getContext("style")
export let logoUrl
export let title
export let styles
const logOut = () => {
authStore.actions.logOut()

View File

@ -1,5 +1,7 @@
<script>
import DataProvider from "./DataProvider.svelte"
import { getContext } from "svelte"
const { DataProvider } = getContext("sdk")
export let table
</script>

View File

@ -2,7 +2,7 @@
import { getContext } from "svelte"
import { RichText } from "@budibase/bbui"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
export let value = ""

View File

@ -1,8 +1,7 @@
<script>
import { onMount, getContext } from "svelte"
import DataProvider from "./DataProvider.svelte"
const { API, screenStore, routeStore } = getContext("app")
const { API, screenStore, routeStore, DataProvider } = getContext("sdk")
export let table

View File

@ -1,9 +1,9 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let styles
export let imageUrl = ""
export let heading = ""
export let text1 = ""

View File

@ -1,12 +1,12 @@
<script>
import { getContext } from "svelte"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let text = ""
export let className = ""
export let type = ""
export let styles
const isTag = tag => type === tag
</script>

View File

@ -2,7 +2,7 @@
import { Dropzone } from "@budibase/bbui"
import { getContext } from "svelte"
const { API } = getContext("app")
const { API } = getContext("sdk")
const BYTES_IN_MB = 1000000
export let files = []

View File

@ -2,10 +2,10 @@
import { getContext } from "svelte"
import { chart } from "svelte-apexcharts"
const { styleable } = getContext("app")
const { styleable } = getContext("sdk")
const styles = getContext("style")
export let options
export let styles
</script>
{#if options}

View File

@ -4,7 +4,7 @@
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
const { API } = getContext("app")
const { API } = getContext("sdk")
const dataContext = getContext("data")
export let title
@ -22,7 +22,6 @@
export let stacked
export let yAxisUnits
export let palette
export let styles
let options

View File

@ -4,7 +4,7 @@
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
const { API } = getContext("app")
const { API } = getContext("sdk")
const dataContext = getContext("data")
export let title
@ -20,7 +20,6 @@
export let width
export let animate
export let yAxisUnits
export let styles
let options

View File

@ -4,7 +4,7 @@
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
const { API } = getContext("app")
const { API } = getContext("sdk")
const dataContext = getContext("data")
// Common props
@ -23,7 +23,6 @@
export let legend
export let yAxisUnits
export let palette
export let styles
// Area specific props
export let area

View File

@ -4,7 +4,7 @@
import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp"
const { API } = getContext("app")
const { API } = getContext("sdk")
const dataContext = getContext("data")
export let title
@ -19,7 +19,6 @@
export let legend
export let donut
export let palette
export let styles
let options

View File

@ -14,8 +14,9 @@
// These maps need to be set up to handle whatever types that are used in the tables.
const setters = new Map([["number", number]])
const SDK = getContext("app")
const SDK = getContext("sdk")
const dataContext = getContext("data")
const styles = getContext("style")
const { API, styleable } = SDK
export let datasource = {}
@ -24,7 +25,6 @@
export let height = 500
export let pagination
export let detailUrl
export let styles
// Add setting height as css var to allow grid to use correct height
styles.normal["--grid-height"] = `${height}px`

View File

@ -5,7 +5,7 @@
import debounce from "lodash.debounce"
const dispatch = createEventDispatcher()
const { fetchRow, saveRow, routeStore } = getContext("app")
const { fetchRow, saveRow, routeStore } = getContext("sdk")
const DEFAULTS_FOR_TYPE = {
string: "",

View File

@ -1,26 +0,0 @@
import { getContext } from "svelte"
import { writable } from "svelte/store"
export const createDataProviderStore = () => {
const { API } = getContext("app")
const store = writable({
row: {},
table: null,
})
const setRow = async row => {
let table
if (row && row.tableId) {
table = await API.fetchTableDefinition(row.tableId)
}
store.update(state => {
state.row = row
state.table = table
return state
})
}
return {
subscribe: store.subscribe,
actions: { setRow },
}
}