1
0
Fork 0
mirror of synced 2024-06-30 03:50:37 +12:00

Add support for full depth data binding

This commit is contained in:
Andrew Kingston 2020-11-20 09:50:10 +00:00
parent 624078636b
commit 32a143cf41
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. * 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) { if (!datasource || !datasource.type) {
return [] return []
} }
@ -20,9 +20,9 @@ export const fetchDatasource = async (datasource, context) => {
rows = await fetchViewData(datasource) rows = await fetchViewData(datasource)
} else if (type === "link") { } else if (type === "link") {
rows = await fetchRelationshipData({ rows = await fetchRelationshipData({
tableId: context?.row?.tableId, rowId: dataContext?.data?._id,
tableId: dataContext?.data?.tableId,
fieldName, fieldName,
rowId: context?.row?._id,
}) })
} }

View file

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

View file

@ -1,21 +1,10 @@
<script> <script>
import { getContext } from "svelte" import { getContext, setContext } from "svelte"
import * as ComponentLibrary from "@budibase/standard-components" import * as ComponentLibrary from "@budibase/standard-components"
import Router from "./Router.svelte" import Router from "./Router.svelte"
import enrichDataBinding from "../utils/enrichDataBinding"
const dataProviderStore = getContext("data")
export let definition = {} 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 // Extracts the actual component name from the library name
const extractComponentName = name => { const extractComponentName = name => {
const split = name?.split("/") const split = name?.split("/")
@ -23,13 +12,12 @@
} }
// Extracts valid props to pass to the real svelte component // Extracts valid props to pass to the real svelte component
const extractValidProps = (component, row) => { const extractValidProps = component => {
let props = {} let props = {}
const enrich = value => enrichDataBinding(value, { data: row })
Object.entries(component) Object.entries(component)
.filter(([name]) => !name.startsWith("_")) .filter(([name]) => !name.startsWith("_"))
.forEach(([key, value]) => { .forEach(([key, value]) => {
props[key] = row === undefined ? value : enrich(value) props[key] = value
}) })
return props return props
} }
@ -39,11 +27,22 @@
return name === "screenslot" ? Router : ComponentLibrary[componentName] 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) $: console.log("Rendering: " + componentName)
</script> </script>
{#if constructor} {#if constructor}
<svelte:component this={constructor} {...componentProps} {styles}> <svelte:component this={constructor} {...enrichedProps}>
{#if children && children.length} {#if children && children.length}
{#each children as child} {#each children as child}
<svelte:self definition={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> <script>
import { onMount } from "svelte" import { getContext } from "svelte"
import Router from "svelte-spa-router" import Router from "svelte-spa-router"
import { routeStore, screenStore } from "../store" import { routeStore, screenStore } from "../store"
import Screen from "./Screen.svelte" import Screen from "./Screen.svelte"
import { styleable } from "../utils"
export let styles const { styleable } = getContext("sdk")
const styles = getContext("style")
$: routerConfig = getRouterConfig($routeStore.routes) $: routerConfig = getRouterConfig($routeStore.routes)
const getRouterConfig = routes => { const getRouterConfig = routes => {

View file

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

View file

@ -1,7 +1,6 @@
import * as API from "../api" import * as API from "../api"
import { getAppId } from "../utils" import { getAppId } from "../utils"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { loc } from "svelte-spa-router"
const createAuthStore = () => { const createAuthStore = () => {
const store = writable("") 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 { authStore } from "./auth"
export { routeStore } from "./routes" export { routeStore } from "./routes"
export { screenStore } from "./screens" 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. * Enriches a given input with a row from the database.
*/ */
export default (input, context) => { export const enrichDataBinding = (input, context) => {
// Only accept string inputs // Only accept string inputs
if (!input || typeof input !== "string") { if (!input || typeof input !== "string") {
return input return input
@ -28,6 +28,8 @@ export default (input, context) => {
if (!looksLikeMustache.test(input)) { if (!looksLikeMustache.test(input)) {
return input return input
} }
console.log("====================================")
console.log(input) console.log(input)
console.log(context)
return mustache.render(input, context) return mustache.render(input, context)
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,5 @@
<script> <script>
import Form from "./Form.svelte" import Form from "./Form.svelte"
export let styles
</script> </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 { DatePicker } from "@budibase/bbui"
import { getContext } from "svelte" import { getContext } from "svelte"
const { styleable } = getContext("app") const { styleable } = getContext("sdk")
const styles = getContext("style")
export let placeholder export let placeholder
export let value export let value
export let styles
function handleChange(event) { function handleChange(event) {
const [fullDate] = event.detail const [fullDate] = event.detail

View file

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

View file

@ -5,15 +5,26 @@
import LinkedRowSelector from "./LinkedRowSelector.svelte" import LinkedRowSelector from "./LinkedRowSelector.svelte"
import { capitalise } from "./helpers" import { capitalise } from "./helpers"
const { styleable, screenStore } = getContext("app") const { styleable, screenStore, API } = getContext("sdk")
const dataProviderStore = getContext("data") const dataContextStore = getContext("data")
const styles = getContext("style")
export let wide = false export let wide = false
export let styles
$: row = $dataProviderStore?.row let row
$: schema = $dataProviderStore?.table && $dataProviderStore.table.schema let schema
$: fields = schema ? Object.keys(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> </script>
<div class="form-content" use:styleable={styles}> <div class="form-content" use:styleable={styles}>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,14 @@
<script> <script>
import { getContext } from "svelte" 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 buttonText = "Log In"
export let logo = "" export let logo = ""
export let title = "" export let title = ""
export let buttonClass = "" export let buttonClass = ""
export let inputClass = "" export let inputClass = ""
export let styles
let username = "" let username = ""
let password = "" let password = ""

View file

@ -1,11 +1,11 @@
<script> <script>
import { getContext } from "svelte" 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 logoUrl
export let title export let title
export let styles
const logOut = () => { const logOut = () => {
authStore.actions.logOut() authStore.actions.logOut()

View file

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

View file

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

View file

@ -1,8 +1,7 @@
<script> <script>
import { onMount, getContext } from "svelte" 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 export let table

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@
import ApexChart from "./ApexChart.svelte" import ApexChart from "./ApexChart.svelte"
import { isEmpty } from "lodash/fp" import { isEmpty } from "lodash/fp"
const { API } = getContext("app") const { API } = getContext("sdk")
const dataContext = getContext("data") const dataContext = getContext("data")
export let title export let title
@ -19,7 +19,6 @@
export let legend export let legend
export let donut export let donut
export let palette export let palette
export let styles
let options 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. // These maps need to be set up to handle whatever types that are used in the tables.
const setters = new Map([["number", number]]) const setters = new Map([["number", number]])
const SDK = getContext("app") const SDK = getContext("sdk")
const dataContext = getContext("data") const dataContext = getContext("data")
const styles = getContext("style")
const { API, styleable } = SDK const { API, styleable } = SDK
export let datasource = {} export let datasource = {}
@ -24,7 +25,6 @@
export let height = 500 export let height = 500
export let pagination export let pagination
export let detailUrl export let detailUrl
export let styles
// Add setting height as css var to allow grid to use correct height // Add setting height as css var to allow grid to use correct height
styles.normal["--grid-height"] = `${height}px` styles.normal["--grid-height"] = `${height}px`

View file

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