From e7378ede975aba32d88ffe7450142c050510b23a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 24 Jan 2025 14:50:27 +0000 Subject: [PATCH 01/11] Add ability to customise format of grid columns via bindings --- .../controls/GridColumnConfiguration/getColumns.js | 2 ++ packages/client/manifest.json | 8 +++++++- .../client/src/components/app/GridBlock.svelte | 14 +++++++++++++- .../src/components/grid/cells/DataCell.svelte | 5 ++++- .../src/components/grid/stores/columns.ts | 1 + packages/types/src/ui/stores/grid/columns.ts | 1 + 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js index d3f081756d..638c41e0ec 100644 --- a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js +++ b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js @@ -69,6 +69,7 @@ const toGridFormat = draggableListColumns => { active: entry.active, width: entry.width, conditions: entry.conditions, + format: entry.format, })) } @@ -85,6 +86,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => { columnType: column.columnType || schema[column.field].type, width: column.width, conditions: column.conditions, + format: column.format, }, {} ) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index c236dd1ad9..a2db6eca6d 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -3089,6 +3089,11 @@ "type": "tableConditions", "label": "Conditions", "key": "conditions" + }, + { + "type": "text", + "label": "Format", + "key": "format" } ] }, @@ -7685,7 +7690,8 @@ { "type": "columns/grid", "key": "columns", - "resetOn": "table" + "resetOn": "table", + "nested": true } ] }, diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 4a8dfb4fcb..03494d24dd 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -5,6 +5,7 @@ import { get, derived, readable } from "svelte/store" import { featuresStore } from "stores" import { Grid } from "@budibase/frontend-core" + import { processStringSync } from "@budibase/string-templates" // table is actually any datasource, but called table for legacy compatibility export let table @@ -42,6 +43,7 @@ let gridContext let minHeight = 0 + $: id = $component.id $: currentTheme = $context?.device?.theme $: darkMode = !currentTheme?.includes("light") $: parsedColumns = getParsedColumns(columns) @@ -65,7 +67,6 @@ const clean = gridContext?.rows.actions.cleanRow || (x => x) const cleaned = rows.map(clean) const goldenRow = generateGoldenSample(cleaned) - const id = get(component).id return { // Not sure what this one is for... [id]: goldenRow, @@ -104,6 +105,7 @@ order: idx, conditions: column.conditions, visible: !!column.active, + format: createFormatter(column), } if (column.width) { overrides[column.field].width = column.width @@ -112,6 +114,16 @@ return overrides } + const createFormatter = column => { + if (!column.format?.length) { + return null + } + return row => { + console.log("processStringSync") + return processStringSync(column.format, { [id]: row }) + } + } + const enrichButtons = buttons => { if (!buttons?.length) { return null diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index cd6b61af80..cdd0d50093 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -3,6 +3,7 @@ import GridCell from "./GridCell.svelte" import { getCellRenderer } from "../lib/renderers" import { derived, writable } from "svelte/store" + import { processStringSync } from "@budibase/string-templates" const { rows, @@ -36,6 +37,8 @@ let api + $: value = column.format ? column.format(row) : row[column.name] + // Get the error for this cell if the cell is focused or selected $: error = getErrorStore(rowFocused, cellId) @@ -138,7 +141,7 @@ { conditions: fieldSchema.conditions, related: fieldSchema.related, calculationType: fieldSchema.calculationType, + format: fieldSchema.format, __left: undefined as any, // TODO __idx: undefined as any, // TODO } diff --git a/packages/types/src/ui/stores/grid/columns.ts b/packages/types/src/ui/stores/grid/columns.ts index 2517d2a3e0..aa5124ba04 100644 --- a/packages/types/src/ui/stores/grid/columns.ts +++ b/packages/types/src/ui/stores/grid/columns.ts @@ -4,6 +4,7 @@ export type UIColumn = FieldSchema & { label: string readonly: boolean conditions: any + format?: () => any related?: { field: string subField: string From ba1568882d33b36bd643f5a20f7917b7aa412d12 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 29 Jan 2025 16:03:58 +0000 Subject: [PATCH 02/11] Remove log --- packages/client/src/components/app/GridBlock.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 03494d24dd..4da5ed4e28 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -119,7 +119,6 @@ return null } return row => { - console.log("processStringSync") return processStringSync(column.format, { [id]: row }) } } From 4614a13abf21b845f6d362acc940f535f821a7b5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 08:47:35 +0000 Subject: [PATCH 03/11] Lint --- packages/frontend-core/src/components/grid/cells/DataCell.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index cdd0d50093..9edaa6af87 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -3,7 +3,6 @@ import GridCell from "./GridCell.svelte" import { getCellRenderer } from "../lib/renderers" import { derived, writable } from "svelte/store" - import { processStringSync } from "@budibase/string-templates" const { rows, From cda47842730443d9bcbf02475c257c89bc11a7f9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 09:58:23 +0000 Subject: [PATCH 04/11] Add info to format and ensure all custom-formatted cells are displayed as plain text --- .../design/settings/controls/PropertyControl.svelte | 2 +- packages/client/manifest.json | 3 ++- packages/frontend-core/src/components/grid/lib/renderers.ts | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index 65b3ed9395..55680160a2 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -172,7 +172,7 @@ } .text { font-size: var(--spectrum-global-dimension-font-size-75); - color: var(--grey-6); + color: var(--spectrum-global-color-gray-700); grid-column: 2 / 2; } diff --git a/packages/client/manifest.json b/packages/client/manifest.json index a2db6eca6d..c451fc0905 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -3093,7 +3093,8 @@ { "type": "text", "label": "Format", - "key": "format" + "key": "format", + "info": "Changing the format will display the value as plain text" } ] }, diff --git a/packages/frontend-core/src/components/grid/lib/renderers.ts b/packages/frontend-core/src/components/grid/lib/renderers.ts index b009806cc4..c1b979dd99 100644 --- a/packages/frontend-core/src/components/grid/lib/renderers.ts +++ b/packages/frontend-core/src/components/grid/lib/renderers.ts @@ -50,10 +50,12 @@ function getCellRendererByType(type: FieldType | "role" | undefined) { } export const getCellRenderer = (column: UIColumn) => { + if (column.format) { + return TextCell + } if (column.calculationType) { return NumberCell } - return ( getCellRendererByType(column.schema?.cellRenderType) || getCellRendererByType(column.schema?.type) || From dd3c68ec0f19440a1180777c291c0a86e6960538 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 13:40:14 +0000 Subject: [PATCH 05/11] Ensure new row cells don't use custom formats --- .../src/components/grid/cells/DataCell.svelte | 8 ++++++-- .../frontend-core/src/components/grid/lib/renderers.ts | 3 --- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index 9edaa6af87..4f181ab12e 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -3,6 +3,7 @@ import GridCell from "./GridCell.svelte" import { getCellRenderer } from "../lib/renderers" import { derived, writable } from "svelte/store" + import TextCell from "./TextCell.svelte" const { rows, @@ -36,7 +37,10 @@ let api - $: value = column.format ? column.format(row) : row[column.name] + // Get the appropriate cell renderer and value + $: hasCustomFormat = column.format && !row._isNewRow + $: renderer = hasCustomFormat ? TextCell : getCellRenderer(column) + $: value = hasCustomFormat ? column.format(row) : row[column.name] // Get the error for this cell if the cell is focused or selected $: error = getErrorStore(rowFocused, cellId) @@ -138,7 +142,7 @@ }} > { - if (column.format) { - return TextCell - } if (column.calculationType) { return NumberCell } From 301bedb5f49de6cb1c9dca95ba9058995840a720 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 13:40:30 +0000 Subject: [PATCH 06/11] Ensure custom format cells are readonly --- packages/frontend-core/src/components/grid/cells/DataCell.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index 4f181ab12e..395f2da6e1 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -47,6 +47,7 @@ // Determine if the cell is editable $: readonly = + hasCustomFormat || columns.actions.isReadonly(column) || (!$config.canEditRows && !row._isNewRow) From d37fedaae7a6bd752f482f29a1df957c11bce687 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 13:46:10 +0000 Subject: [PATCH 07/11] Copy formatted value when copying values from cells --- .../frontend-core/src/components/grid/cells/DataCell.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index 395f2da6e1..bdf16eab7d 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -76,7 +76,7 @@ onKeyDown: (...params) => api?.onKeyDown?.(...params), isReadonly: () => readonly, getType: () => column.schema.type, - getValue: () => row[column.name], + getValue: () => value, setValue: (value, options = { apply: true }) => { validation.actions.setError(cellId, null) updateValue({ From 12f9d7efb6e56aa6259450622799597a44f54ebb Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 15:11:21 +0000 Subject: [PATCH 08/11] Reduce frequency of column formatter invocation by caching formatted display values into rows --- .../src/components/app/GridBlock.svelte | 6 +-- .../src/components/grid/cells/DataCell.svelte | 2 +- .../src/components/grid/stores/rows.ts | 54 +++++++++++++------ packages/types/src/ui/stores/grid/columns.ts | 4 +- packages/types/src/ui/stores/grid/table.ts | 4 +- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 4da5ed4e28..1f4792ea8a 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -115,12 +115,10 @@ } const createFormatter = column => { - if (!column.format?.length) { + if (typeof column.format !== "string" || !column.format.trim().length) { return null } - return row => { - return processStringSync(column.format, { [id]: row }) - } + return row => processStringSync(column.format, { [id]: row }) } const enrichButtons = buttons => { diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index bdf16eab7d..486d9e1952 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -40,7 +40,7 @@ // Get the appropriate cell renderer and value $: hasCustomFormat = column.format && !row._isNewRow $: renderer = hasCustomFormat ? TextCell : getCellRenderer(column) - $: value = hasCustomFormat ? column.format(row) : row[column.name] + $: value = hasCustomFormat ? row.__formatted?.[column.name] : row[column.name] // Get the error for this cell if the cell is focused or selected $: error = getErrorStore(rowFocused, cellId) diff --git a/packages/frontend-core/src/components/grid/stores/rows.ts b/packages/frontend-core/src/components/grid/stores/rows.ts index 7e58808327..22357f66a7 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.ts +++ b/packages/frontend-core/src/components/grid/stores/rows.ts @@ -16,6 +16,7 @@ import { Store as StoreContext } from "." interface IndexedUIRow extends UIRow { __idx: number + __formatted: Record } interface RowStore { @@ -114,26 +115,44 @@ export const createStores = (): RowStore => { export const deriveStores = (context: StoreContext): RowDerivedStore => { const { rows, enrichedSchema } = context - // Enrich rows with an index property and any pending changes + // Enrich rows with an index property and additional values const enrichedRows = derived( [rows, enrichedSchema], ([$rows, $enrichedSchema]) => { - const customColumns = Object.values($enrichedSchema || {}).filter( - f => f.related - ) - return $rows.map((row, idx) => ({ - ...row, - __idx: idx, - ...customColumns.reduce>((map, column) => { - const fromField = $enrichedSchema![column.related!.field] - map[column.name] = getRelatedTableValues( - row, - { ...column, related: column.related! }, - fromField - ) - return map - }, {}), - })) + // Find columns which require additional processing + const cols = Object.values($enrichedSchema || {}) + const relatedColumns = cols.filter(col => col.related) + const formattedColumns = cols.filter(col => col.format) + + return $rows.map((row, idx) => { + // Derive any values that need enriched from related rows + const relatedValues = relatedColumns.reduce>( + (map, column) => { + const fromField = $enrichedSchema![column.related!.field] + map[column.name] = getRelatedTableValues( + row, + { ...column, related: column.related! }, + fromField + ) + return map + }, + {} + ) + // Derive any display-only formatted values for this row + const formattedValues = formattedColumns.reduce>( + (map, column) => { + map[column.name] = column.format!(row) + return map + }, + {} + ) + return { + ...row, + ...relatedValues, + __formatted: formattedValues, + __idx: idx, + } + }) } ) @@ -791,6 +810,7 @@ export const createActions = (context: StoreContext): RowActionStore => { let clone: Row = { ...row } delete clone.__idx delete clone.__metadata + delete clone.__formatted if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id!)) { delete clone._id } diff --git a/packages/types/src/ui/stores/grid/columns.ts b/packages/types/src/ui/stores/grid/columns.ts index aa5124ba04..c6ea801713 100644 --- a/packages/types/src/ui/stores/grid/columns.ts +++ b/packages/types/src/ui/stores/grid/columns.ts @@ -1,10 +1,10 @@ -import { CalculationType, FieldSchema, FieldType } from "@budibase/types" +import { CalculationType, FieldSchema, FieldType, UIRow } from "@budibase/types" export type UIColumn = FieldSchema & { label: string readonly: boolean conditions: any - format?: () => any + format?: (row: UIRow) => any related?: { field: string subField: string diff --git a/packages/types/src/ui/stores/grid/table.ts b/packages/types/src/ui/stores/grid/table.ts index 7b6d659e4c..5eea063084 100644 --- a/packages/types/src/ui/stores/grid/table.ts +++ b/packages/types/src/ui/stores/grid/table.ts @@ -5,6 +5,7 @@ import { RelationSchemaField, SortOrder, Table, + UIRow, UISearchFilter, } from "@budibase/types" @@ -24,9 +25,10 @@ export interface UITable extends Table { export type UIFieldSchema = FieldSchema & BasicViewFieldMetadata & { related?: { field: string; subField: string } - columns?: Record + columns?: Record cellRenderType?: string disabled?: boolean + format?: (row: UIRow) => any } interface UIRelationSchemaField extends RelationSchemaField { From 60fd8fb1e1001b13d794a2c9a1a29ccea17c133d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 15:14:49 +0000 Subject: [PATCH 09/11] Fix typo --- packages/types/src/ui/stores/grid/table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/ui/stores/grid/table.ts b/packages/types/src/ui/stores/grid/table.ts index 5eea063084..9a0a54c5c6 100644 --- a/packages/types/src/ui/stores/grid/table.ts +++ b/packages/types/src/ui/stores/grid/table.ts @@ -25,7 +25,7 @@ export interface UITable extends Table { export type UIFieldSchema = FieldSchema & BasicViewFieldMetadata & { related?: { field: string; subField: string } - columns?: Record + columns?: Record cellRenderType?: string disabled?: boolean format?: (row: UIRow) => any From 5513293ef6ebc7ebb0954be015967a6adb727aa7 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 30 Jan 2025 15:17:41 +0000 Subject: [PATCH 10/11] Shorten info text to fit in one line --- packages/client/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index c451fc0905..4b13713984 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -3094,7 +3094,7 @@ "type": "text", "label": "Format", "key": "format", - "info": "Changing the format will display the value as plain text" + "info": "Changing format will display values as text" } ] }, From 3ecc8f0cbbf0679d49a6e8be2a3dbe0364b3ad32 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 3 Feb 2025 13:48:34 +0000 Subject: [PATCH 11/11] Add missing bindings to grid column configuration primary display --- .../GridColumnConfiguration/GridColumnConfiguration.svelte | 1 + .../GridColumnConfiguration/PrimaryColumnFieldSetting.svelte | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte index 46aea2a6c4..fb3856d517 100644 --- a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte @@ -61,6 +61,7 @@ anchor={primaryDisplayColumnAnchor} item={columns.primary} on:change={e => columns.update(e.detail)} + {bindings} /> diff --git a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte index c4c2b3eafb..b9f7ab976b 100644 --- a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte @@ -8,6 +8,7 @@ export let item export let anchor + export let bindings let draggableStore = writable({ selected: null, @@ -48,6 +49,7 @@ componentInstance={item} {parseSettings} on:change + {bindings} >