From 5f6b846937161accd2100b5bdef2881359d8e5b6 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 16 Feb 2024 13:25:41 +0000 Subject: [PATCH] Add ability for components to provide example data context that they provide so that more bindings work with live eval --- .../common/bindings/BindingPicker.svelte | 2 - .../client/src/components/Component.svelte | 11 ++++- .../src/components/app/GridBlock.svelte | 14 ++++++ packages/client/src/sdk.js | 2 + packages/client/src/utils/components.js | 49 +++++++++++++++++++ 5 files changed, 75 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index cb990b277a..5ff304fdb2 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -69,8 +69,6 @@ return names } - $: console.log(context) - const getBindingValue = binding => { const hbs = `{{ ${binding.runtimeBinding} }}` return processStringSync(hbs, context) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 6d6af91dcc..e7024ac17f 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -575,6 +575,15 @@ } } + const getDataContext = () => { + const normalContext = get(context) + const additionalContext = ref?.getAdditionalDataContext?.() + return { + ...normalContext, + ...additionalContext, + } + } + onMount(() => { // Register this component instance for external access if ($appStore.isDevApp) { @@ -583,7 +592,7 @@ component: instance._component, getSettings: () => cachedSettings, getRawSettings: () => ({ ...staticSettings, ...dynamicSettings }), - getDataContext: () => get(context), + getDataContext: getDataContext, reload: () => initialise(instance, true), setEphemeralStyles: styles => (ephemeralStyles = styles), state: store, diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 0a343cac51..c2bc9f3989 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -30,6 +30,7 @@ ActionTypes, createContextStore, Provider, + generateGoldenSample, } = getContext("sdk") let grid @@ -48,6 +49,19 @@ }, ] + // Provide additional data context for live binding eval + export const getAdditionalDataContext = () => { + const rows = get(grid?.getContext()?.rows) + const goldenRow = generateGoldenSample(rows) + const id = [get(component).id] + return { + [id]: goldenRow, + eventContext: { + row: goldenRow, + }, + } + } + // Parses columns to fix older formats const getParsedColumns = columns => { // If the first element has an active key all elements should be in the new format diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index 557d5ca8da..c0b99cd131 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -28,6 +28,7 @@ import { ActionTypes } from "./constants" import { fetchDatasourceSchema } from "./utils/schema.js" import { getAPIKey } from "./utils/api.js" import { enrichButtonActions } from "./utils/buttonActions.js" +import { generateGoldenSample } from "./utils/components.js" import { processStringSync, makePropSafe } from "@budibase/string-templates" import { fetchData, LuceneUtils, Constants } from "@budibase/frontend-core" @@ -65,6 +66,7 @@ export default { processStringSync, makePropSafe, createContextStore, + generateGoldenSample, // Components Provider, diff --git a/packages/client/src/utils/components.js b/packages/client/src/utils/components.js index 1812175c2c..70f80e4329 100644 --- a/packages/client/src/utils/components.js +++ b/packages/client/src/utils/components.js @@ -82,3 +82,52 @@ export const findComponentParent = (rootComponent, id, parentComponent) => { } return null } + +/** + * Util to check is a given value is "better" than another. "Betterness" is + * defined as presence and length. + */ +const isBetterSample = (newValue, oldValue) => { + // Prefer non-null values + if (oldValue == null && newValue != null) { + return true + } + + // Don't change type + const oldType = typeof oldValue + const newType = typeof newValue + if (oldType !== newType) { + return false + } + + // Prefer longer values + if (newType === "string" && newValue.length > oldValue.length) { + return true + } + if ( + newType === "object" && + Object.keys(newValue).length > Object.keys(oldValue).length + ) { + return true + } + + return false +} + +/** + * Generates a best-case example object of the provided samples. + * The generated sample does not necessarily exist - it simply is a sample that + * contains "good" examples for every property of all the samples. + * The generate sample will have a value for all keys across all samples. + */ +export const generateGoldenSample = samples => { + let goldenSample = {} + samples?.forEach(sample => { + Object.keys(sample).forEach(key => { + if (isBetterSample(sample[key], goldenSample[key])) { + goldenSample[key] = sample[key] + } + }) + }) + return goldenSample +}