Merge pull request #15457 from Budibase/grid-column-formatting
Table column "format" setting
This commit is contained in:
commit
3b58dca570
|
@ -61,6 +61,7 @@
|
||||||
anchor={primaryDisplayColumnAnchor}
|
anchor={primaryDisplayColumnAnchor}
|
||||||
item={columns.primary}
|
item={columns.primary}
|
||||||
on:change={e => columns.update(e.detail)}
|
on:change={e => columns.update(e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
export let item
|
export let item
|
||||||
export let anchor
|
export let anchor
|
||||||
|
export let bindings
|
||||||
|
|
||||||
let draggableStore = writable({
|
let draggableStore = writable({
|
||||||
selected: null,
|
selected: null,
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
componentInstance={item}
|
componentInstance={item}
|
||||||
{parseSettings}
|
{parseSettings}
|
||||||
on:change
|
on:change
|
||||||
|
{bindings}
|
||||||
>
|
>
|
||||||
<div slot="header" class="type-icon">
|
<div slot="header" class="type-icon">
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
|
|
|
@ -69,6 +69,7 @@ const toGridFormat = draggableListColumns => {
|
||||||
active: entry.active,
|
active: entry.active,
|
||||||
width: entry.width,
|
width: entry.width,
|
||||||
conditions: entry.conditions,
|
conditions: entry.conditions,
|
||||||
|
format: entry.format,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +86,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
|
||||||
columnType: column.columnType || schema[column.field].type,
|
columnType: column.columnType || schema[column.field].type,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
conditions: column.conditions,
|
conditions: column.conditions,
|
||||||
|
format: column.format,
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
|
|
@ -165,7 +165,7 @@
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
color: var(--grey-6);
|
color: var(--spectrum-global-color-gray-700);
|
||||||
grid-column: 2 / 2;
|
grid-column: 2 / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3089,6 +3089,12 @@
|
||||||
"type": "tableConditions",
|
"type": "tableConditions",
|
||||||
"label": "Conditions",
|
"label": "Conditions",
|
||||||
"key": "conditions"
|
"key": "conditions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Format",
|
||||||
|
"key": "format",
|
||||||
|
"info": "Changing format will display values as text"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -7685,7 +7691,8 @@
|
||||||
{
|
{
|
||||||
"type": "columns/grid",
|
"type": "columns/grid",
|
||||||
"key": "columns",
|
"key": "columns",
|
||||||
"resetOn": "table"
|
"resetOn": "table",
|
||||||
|
"nested": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { get, derived, readable } from "svelte/store"
|
import { get, derived, readable } from "svelte/store"
|
||||||
import { featuresStore } from "stores"
|
import { featuresStore } from "stores"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
|
||||||
// table is actually any datasource, but called table for legacy compatibility
|
// table is actually any datasource, but called table for legacy compatibility
|
||||||
export let table
|
export let table
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
let gridContext
|
let gridContext
|
||||||
let minHeight = 0
|
let minHeight = 0
|
||||||
|
|
||||||
|
$: id = $component.id
|
||||||
$: currentTheme = $context?.device?.theme
|
$: currentTheme = $context?.device?.theme
|
||||||
$: darkMode = !currentTheme?.includes("light")
|
$: darkMode = !currentTheme?.includes("light")
|
||||||
$: parsedColumns = getParsedColumns(columns)
|
$: parsedColumns = getParsedColumns(columns)
|
||||||
|
@ -65,7 +67,6 @@
|
||||||
const clean = gridContext?.rows.actions.cleanRow || (x => x)
|
const clean = gridContext?.rows.actions.cleanRow || (x => x)
|
||||||
const cleaned = rows.map(clean)
|
const cleaned = rows.map(clean)
|
||||||
const goldenRow = generateGoldenSample(cleaned)
|
const goldenRow = generateGoldenSample(cleaned)
|
||||||
const id = get(component).id
|
|
||||||
return {
|
return {
|
||||||
// Not sure what this one is for...
|
// Not sure what this one is for...
|
||||||
[id]: goldenRow,
|
[id]: goldenRow,
|
||||||
|
@ -104,6 +105,7 @@
|
||||||
order: idx,
|
order: idx,
|
||||||
conditions: column.conditions,
|
conditions: column.conditions,
|
||||||
visible: !!column.active,
|
visible: !!column.active,
|
||||||
|
format: createFormatter(column),
|
||||||
}
|
}
|
||||||
if (column.width) {
|
if (column.width) {
|
||||||
overrides[column.field].width = column.width
|
overrides[column.field].width = column.width
|
||||||
|
@ -112,6 +114,13 @@
|
||||||
return overrides
|
return overrides
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createFormatter = column => {
|
||||||
|
if (typeof column.format !== "string" || !column.format.trim().length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return row => processStringSync(column.format, { [id]: row })
|
||||||
|
}
|
||||||
|
|
||||||
const enrichButtons = buttons => {
|
const enrichButtons = buttons => {
|
||||||
if (!buttons?.length) {
|
if (!buttons?.length) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import GridCell from "./GridCell.svelte"
|
import GridCell from "./GridCell.svelte"
|
||||||
import { getCellRenderer } from "../lib/renderers"
|
import { getCellRenderer } from "../lib/renderers"
|
||||||
import { derived, writable } from "svelte/store"
|
import { derived, writable } from "svelte/store"
|
||||||
|
import TextCell from "./TextCell.svelte"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
rows,
|
rows,
|
||||||
|
@ -36,11 +37,17 @@
|
||||||
|
|
||||||
let api
|
let api
|
||||||
|
|
||||||
|
// Get the appropriate cell renderer and value
|
||||||
|
$: hasCustomFormat = column.format && !row._isNewRow
|
||||||
|
$: renderer = hasCustomFormat ? TextCell : getCellRenderer(column)
|
||||||
|
$: value = hasCustomFormat ? row.__formatted?.[column.name] : row[column.name]
|
||||||
|
|
||||||
// Get the error for this cell if the cell is focused or selected
|
// Get the error for this cell if the cell is focused or selected
|
||||||
$: error = getErrorStore(rowFocused, cellId)
|
$: error = getErrorStore(rowFocused, cellId)
|
||||||
|
|
||||||
// Determine if the cell is editable
|
// Determine if the cell is editable
|
||||||
$: readonly =
|
$: readonly =
|
||||||
|
hasCustomFormat ||
|
||||||
columns.actions.isReadonly(column) ||
|
columns.actions.isReadonly(column) ||
|
||||||
(!$config.canEditRows && !row._isNewRow)
|
(!$config.canEditRows && !row._isNewRow)
|
||||||
|
|
||||||
|
@ -69,7 +76,7 @@
|
||||||
onKeyDown: (...params) => api?.onKeyDown?.(...params),
|
onKeyDown: (...params) => api?.onKeyDown?.(...params),
|
||||||
isReadonly: () => readonly,
|
isReadonly: () => readonly,
|
||||||
getType: () => column.schema.type,
|
getType: () => column.schema.type,
|
||||||
getValue: () => row[column.name],
|
getValue: () => value,
|
||||||
setValue: (value, options = { apply: true }) => {
|
setValue: (value, options = { apply: true }) => {
|
||||||
validation.actions.setError(cellId, null)
|
validation.actions.setError(cellId, null)
|
||||||
updateValue({
|
updateValue({
|
||||||
|
@ -136,9 +143,9 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={getCellRenderer(column)}
|
this={renderer}
|
||||||
bind:api
|
bind:api
|
||||||
value={row[column.name]}
|
{value}
|
||||||
schema={column.schema}
|
schema={column.schema}
|
||||||
onChange={cellAPI.setValue}
|
onChange={cellAPI.setValue}
|
||||||
{focused}
|
{focused}
|
||||||
|
|
|
@ -53,7 +53,6 @@ export const getCellRenderer = (column: UIColumn) => {
|
||||||
if (column.calculationType) {
|
if (column.calculationType) {
|
||||||
return NumberCell
|
return NumberCell
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
getCellRendererByType(column.schema?.cellRenderType) ||
|
getCellRendererByType(column.schema?.cellRenderType) ||
|
||||||
getCellRendererByType(column.schema?.type) ||
|
getCellRendererByType(column.schema?.type) ||
|
||||||
|
|
|
@ -188,6 +188,7 @@ export const initialise = (context: StoreContext) => {
|
||||||
conditions: fieldSchema.conditions,
|
conditions: fieldSchema.conditions,
|
||||||
related: fieldSchema.related,
|
related: fieldSchema.related,
|
||||||
calculationType: fieldSchema.calculationType,
|
calculationType: fieldSchema.calculationType,
|
||||||
|
format: fieldSchema.format,
|
||||||
__left: undefined as any, // TODO
|
__left: undefined as any, // TODO
|
||||||
__idx: undefined as any, // TODO
|
__idx: undefined as any, // TODO
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Store as StoreContext } from "."
|
||||||
|
|
||||||
interface IndexedUIRow extends UIRow {
|
interface IndexedUIRow extends UIRow {
|
||||||
__idx: number
|
__idx: number
|
||||||
|
__formatted: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RowStore {
|
interface RowStore {
|
||||||
|
@ -114,17 +115,19 @@ export const createStores = (): RowStore => {
|
||||||
export const deriveStores = (context: StoreContext): RowDerivedStore => {
|
export const deriveStores = (context: StoreContext): RowDerivedStore => {
|
||||||
const { rows, enrichedSchema } = context
|
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(
|
const enrichedRows = derived(
|
||||||
[rows, enrichedSchema],
|
[rows, enrichedSchema],
|
||||||
([$rows, $enrichedSchema]) => {
|
([$rows, $enrichedSchema]) => {
|
||||||
const customColumns = Object.values($enrichedSchema || {}).filter(
|
// Find columns which require additional processing
|
||||||
f => f.related
|
const cols = Object.values($enrichedSchema || {})
|
||||||
)
|
const relatedColumns = cols.filter(col => col.related)
|
||||||
return $rows.map<IndexedUIRow>((row, idx) => ({
|
const formattedColumns = cols.filter(col => col.format)
|
||||||
...row,
|
|
||||||
__idx: idx,
|
return $rows.map<IndexedUIRow>((row, idx) => {
|
||||||
...customColumns.reduce<Record<string, string>>((map, column) => {
|
// Derive any values that need enriched from related rows
|
||||||
|
const relatedValues = relatedColumns.reduce<Record<string, string>>(
|
||||||
|
(map, column) => {
|
||||||
const fromField = $enrichedSchema![column.related!.field]
|
const fromField = $enrichedSchema![column.related!.field]
|
||||||
map[column.name] = getRelatedTableValues(
|
map[column.name] = getRelatedTableValues(
|
||||||
row,
|
row,
|
||||||
|
@ -132,8 +135,24 @@ export const deriveStores = (context: StoreContext): RowDerivedStore => {
|
||||||
fromField
|
fromField
|
||||||
)
|
)
|
||||||
return map
|
return map
|
||||||
}, {}),
|
},
|
||||||
}))
|
{}
|
||||||
|
)
|
||||||
|
// Derive any display-only formatted values for this row
|
||||||
|
const formattedValues = formattedColumns.reduce<Record<string, any>>(
|
||||||
|
(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 }
|
let clone: Row = { ...row }
|
||||||
delete clone.__idx
|
delete clone.__idx
|
||||||
delete clone.__metadata
|
delete clone.__metadata
|
||||||
|
delete clone.__formatted
|
||||||
if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id!)) {
|
if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id!)) {
|
||||||
delete clone._id
|
delete clone._id
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { CalculationType, FieldSchema, FieldType } from "@budibase/types"
|
import { CalculationType, FieldSchema, FieldType, UIRow } from "@budibase/types"
|
||||||
|
|
||||||
export type UIColumn = FieldSchema & {
|
export type UIColumn = FieldSchema & {
|
||||||
label: string
|
label: string
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
conditions: any
|
conditions: any
|
||||||
|
format?: (row: UIRow) => any
|
||||||
related?: {
|
related?: {
|
||||||
field: string
|
field: string
|
||||||
subField: string
|
subField: string
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
RelationSchemaField,
|
RelationSchemaField,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
Table,
|
Table,
|
||||||
|
UIRow,
|
||||||
UISearchFilter,
|
UISearchFilter,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ export type UIFieldSchema = FieldSchema &
|
||||||
columns?: Record<string, UIRelationSchemaField>
|
columns?: Record<string, UIRelationSchemaField>
|
||||||
cellRenderType?: string
|
cellRenderType?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
format?: (row: UIRow) => any
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UIRelationSchemaField extends RelationSchemaField {
|
interface UIRelationSchemaField extends RelationSchemaField {
|
||||||
|
|
Loading…
Reference in New Issue