Grid columns UI (#12209)
* grid columns ui * linting * remove comment * remove property thats not used * remove cli pacakge change * PR Feedback * fixes * fixes * wip * wip * wip * wip * wip * wip * about to implement * wip * wip * wip * wip * wip * wip * wip * tests * linting * remove drag handle file * fix icons * remove field config changes * wip * wip * wip * remove logs * wip * linting * pr feedback * linting
This commit is contained in:
parent
176f7fe404
commit
0d0db98484
|
@ -20,7 +20,7 @@ import ValidationEditor from "./controls/ValidationEditor/ValidationEditor.svelt
|
|||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte"
|
||||
import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
|
||||
import GridColumnEditor from "./controls/ColumnEditor/GridColumnEditor.svelte"
|
||||
import GridColumnEditor from "./controls/GridColumnConfiguration/GridColumnConfiguration.svelte"
|
||||
import BarButtonList from "./controls/BarButtonList.svelte"
|
||||
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
|
||||
import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte"
|
||||
|
@ -28,6 +28,7 @@ import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte
|
|||
|
||||
const componentMap = {
|
||||
text: DrawerBindableInput,
|
||||
plainText: Input,
|
||||
select: Select,
|
||||
radio: RadioGroup,
|
||||
dataSource: DataSourceSelect,
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<script>
|
||||
import ColumnEditor from "./ColumnEditor.svelte"
|
||||
</script>
|
||||
|
||||
<ColumnEditor
|
||||
{...$$props}
|
||||
on:change
|
||||
allowCellEditing={false}
|
||||
allowReorder={false}
|
||||
/>
|
|
@ -0,0 +1,91 @@
|
|||
<script>
|
||||
import EditComponentPopover from "../EditComponentPopover.svelte"
|
||||
import { FieldTypeToComponentMap } from "../FieldConfiguration/utils"
|
||||
import { Toggle, Icon } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { store } from "builderStore"
|
||||
|
||||
export let item
|
||||
export let anchor
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onToggle = item => {
|
||||
return e => {
|
||||
item.active = e.detail
|
||||
dispatch("change", { ...cloneDeep(item), active: e.detail })
|
||||
}
|
||||
}
|
||||
|
||||
const parseSettings = settings => {
|
||||
return settings
|
||||
.filter(setting => setting.key !== "field")
|
||||
.map(setting => {
|
||||
return { ...setting, nested: true }
|
||||
})
|
||||
}
|
||||
|
||||
const getIcon = () => {
|
||||
const component = `@budibase/standard-components/${
|
||||
FieldTypeToComponentMap[item.columnType]
|
||||
}`
|
||||
return store.actions.components.getDefinition(component).icon
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-left">
|
||||
<EditComponentPopover
|
||||
{anchor}
|
||||
componentInstance={item}
|
||||
{parseSettings}
|
||||
on:change
|
||||
>
|
||||
<div slot="header" class="type-icon">
|
||||
<Icon name={getIcon()} />
|
||||
<span>{item.field}</span>
|
||||
</div>
|
||||
</EditComponentPopover>
|
||||
<div class="field-label">{item.label || item.field}</div>
|
||||
</div>
|
||||
<div class="list-item-right">
|
||||
<Toggle on:change={onToggle(item)} text="" value={item.active} thin />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.field-label {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.list-item-body,
|
||||
.list-item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
min-width: 0;
|
||||
}
|
||||
.list-item-right :global(div.spectrum-Switch) {
|
||||
margin: 0px;
|
||||
}
|
||||
.list-item-body {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.type-icon {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
margin: var(--spacing-xl);
|
||||
margin-bottom: 0px;
|
||||
height: var(--spectrum-alias-item-height-m);
|
||||
padding: 0px var(--spectrum-alias-item-padding-m);
|
||||
border-width: var(--spectrum-actionbutton-border-size);
|
||||
border-radius: var(--spectrum-alias-border-radius-regular);
|
||||
border: 1px solid
|
||||
var(
|
||||
--spectrum-actionbutton-m-border-color,
|
||||
var(--spectrum-alias-border-color)
|
||||
);
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,107 @@
|
|||
<script>
|
||||
import {
|
||||
getDatasourceForProvider,
|
||||
getSchemaForDatasource,
|
||||
} from "builderStore/dataBinding"
|
||||
import { currentAsset, store } from "builderStore"
|
||||
import DraggableList from "../DraggableList/DraggableList.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import FieldSetting from "./FieldSetting.svelte"
|
||||
import PrimaryColumnFieldSetting from "./PrimaryColumnFieldSetting.svelte"
|
||||
import getColumns from "./getColumns.js"
|
||||
|
||||
export let value
|
||||
export let componentInstance
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let primaryDisplayColumnAnchor
|
||||
|
||||
const handleChange = newValues => {
|
||||
dispatch("change", newValues)
|
||||
}
|
||||
|
||||
const getSchema = (asset, datasource) => {
|
||||
const schema = getSchemaForDatasource(asset, datasource).schema
|
||||
|
||||
// Don't show ID and rev in tables
|
||||
if (schema) {
|
||||
delete schema._id
|
||||
delete schema._rev
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||
$: primaryDisplayColumnName = getSchemaForDatasource(
|
||||
$currentAsset,
|
||||
datasource
|
||||
)?.table?.primaryDisplay
|
||||
$: schema = getSchema(currentAsset, datasource)
|
||||
$: columns = getColumns({
|
||||
columns: value,
|
||||
schema,
|
||||
primaryDisplayColumnName,
|
||||
onChange: handleChange,
|
||||
createComponent: store.actions.components.createInstance,
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if columns.primary}
|
||||
<div class="sticky-item">
|
||||
<div bind:this={primaryDisplayColumnAnchor} class="sticky-item-inner">
|
||||
<div class="right-content">
|
||||
<PrimaryColumnFieldSetting
|
||||
anchor={primaryDisplayColumnAnchor}
|
||||
item={columns.primary}
|
||||
on:change={e => columns.update(e.detail)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<DraggableList
|
||||
on:change={e => columns.updateSortable(e.detail)}
|
||||
on:itemChange={e => columns.update(e.detail)}
|
||||
items={columns.sortable}
|
||||
listItemKey={"_id"}
|
||||
listType={FieldSetting}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.right-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 17.5px;
|
||||
}
|
||||
.sticky-item {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
background-color: var(
|
||||
--spectrum-table-background-color,
|
||||
var(--spectrum-global-color-gray-50)
|
||||
);
|
||||
border: 1px solid
|
||||
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.sticky-item-inner {
|
||||
background-color: var(
|
||||
--spectrum-table-background-color,
|
||||
var(--spectrum-global-color-gray-50)
|
||||
);
|
||||
transition: background-color ease-in-out 130ms;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid
|
||||
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
|
||||
padding-left: var(--spacing-s);
|
||||
padding-right: var(--spacing-s);
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
<script>
|
||||
import EditComponentPopover from "../EditComponentPopover.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { setContext } from "svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import { FieldTypeToComponentMap } from "../FieldConfiguration/utils"
|
||||
import { store } from "builderStore"
|
||||
|
||||
export let item
|
||||
export let anchor
|
||||
|
||||
let draggableStore = writable({
|
||||
selected: null,
|
||||
actions: {
|
||||
select: id => {
|
||||
draggableStore.update(state => ({
|
||||
...state,
|
||||
selected: id,
|
||||
}))
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setContext("draggable", draggableStore)
|
||||
|
||||
const parseSettings = settings => {
|
||||
return settings
|
||||
.filter(setting => setting.key !== "field")
|
||||
.map(setting => {
|
||||
return { ...setting, nested: true }
|
||||
})
|
||||
}
|
||||
|
||||
const getIcon = item => {
|
||||
const component = `@budibase/standard-components/${
|
||||
FieldTypeToComponentMap[item.columnType]
|
||||
}`
|
||||
return store.actions.components.getDefinition(component)?.icon
|
||||
}
|
||||
|
||||
$: icon = getIcon(item)
|
||||
</script>
|
||||
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-left">
|
||||
<EditComponentPopover
|
||||
{anchor}
|
||||
componentInstance={item}
|
||||
{parseSettings}
|
||||
on:change
|
||||
>
|
||||
<div slot="header" class="type-icon">
|
||||
<Icon name={icon} />
|
||||
<span>{item.field}</span>
|
||||
</div>
|
||||
</EditComponentPopover>
|
||||
<div class="field-label">{item.label || item.field}</div>
|
||||
</div>
|
||||
<div title="The display column is always shown first" class="list-item-right">
|
||||
<Icon name={"Info"} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.field-label {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.list-item-body,
|
||||
.list-item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
min-width: 0;
|
||||
}
|
||||
.list-item-right :global(svg) {
|
||||
color: var(--grey-5);
|
||||
padding: 7px 5px 7px 0;
|
||||
}
|
||||
.list-item-body {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.type-icon {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
margin: var(--spacing-xl);
|
||||
margin-bottom: 0px;
|
||||
height: var(--spectrum-alias-item-height-m);
|
||||
padding: 0px var(--spectrum-alias-item-padding-m);
|
||||
border-width: var(--spectrum-actionbutton-border-size);
|
||||
border-radius: var(--spectrum-alias-border-radius-regular);
|
||||
border: 1px solid
|
||||
var(
|
||||
--spectrum-actionbutton-m-border-color,
|
||||
var(--spectrum-alias-border-color)
|
||||
);
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,129 @@
|
|||
const modernize = columns => {
|
||||
if (!columns) {
|
||||
return []
|
||||
}
|
||||
// If the first element has no active key then it's safe to assume all elements are in the old format
|
||||
if (columns?.[0] && columns[0].active === undefined) {
|
||||
return columns.map(column => ({
|
||||
label: column.displayName,
|
||||
field: column.name,
|
||||
active: true,
|
||||
}))
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
const removeInvalidAddMissing = (
|
||||
columns = [],
|
||||
defaultColumns,
|
||||
primaryDisplayColumnName
|
||||
) => {
|
||||
const defaultColumnNames = defaultColumns.map(column => column.field)
|
||||
const columnNames = columns.map(column => column.field)
|
||||
|
||||
const validColumns = columns.filter(column =>
|
||||
defaultColumnNames.includes(column.field)
|
||||
)
|
||||
let missingColumns = defaultColumns.filter(
|
||||
defaultColumn => !columnNames.includes(defaultColumn.field)
|
||||
)
|
||||
|
||||
// If the user already has fields selected, any appended missing fields should be disabled by default
|
||||
if (validColumns.length) {
|
||||
missingColumns = missingColumns.map(field => ({ ...field, active: false }))
|
||||
}
|
||||
|
||||
const combinedColumns = [...validColumns, ...missingColumns]
|
||||
|
||||
// Ensure the primary display column is always visible
|
||||
const primaryDisplayIndex = combinedColumns.findIndex(
|
||||
column => column.field === primaryDisplayColumnName
|
||||
)
|
||||
if (primaryDisplayIndex > -1) {
|
||||
combinedColumns[primaryDisplayIndex].active = true
|
||||
}
|
||||
|
||||
return combinedColumns
|
||||
}
|
||||
|
||||
const getDefault = (schema = {}) => {
|
||||
const defaultValues = Object.values(schema)
|
||||
.filter(column => !column.nestedJSON)
|
||||
.map(column => ({
|
||||
label: column.name,
|
||||
field: column.name,
|
||||
active: column.visible ?? true,
|
||||
order: column.visible ? column.order ?? -1 : Number.MAX_SAFE_INTEGER,
|
||||
}))
|
||||
|
||||
defaultValues.sort((a, b) => a.order - b.order)
|
||||
|
||||
return defaultValues
|
||||
}
|
||||
|
||||
const toGridFormat = draggableListColumns => {
|
||||
return draggableListColumns.map(entry => ({
|
||||
label: entry.label,
|
||||
field: entry.field,
|
||||
active: entry.active,
|
||||
}))
|
||||
}
|
||||
|
||||
const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
|
||||
return gridFormatColumns.map(column => {
|
||||
return createComponent(
|
||||
"@budibase/standard-components/labelfield",
|
||||
{
|
||||
_instanceName: column.field,
|
||||
active: column.active,
|
||||
field: column.field,
|
||||
label: column.label,
|
||||
columnType: schema[column.field].type,
|
||||
},
|
||||
{}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const getColumns = ({
|
||||
columns,
|
||||
schema,
|
||||
primaryDisplayColumnName,
|
||||
onChange,
|
||||
createComponent,
|
||||
}) => {
|
||||
const validatedColumns = removeInvalidAddMissing(
|
||||
modernize(columns),
|
||||
getDefault(schema),
|
||||
primaryDisplayColumnName
|
||||
)
|
||||
const draggableList = toDraggableListFormat(
|
||||
validatedColumns,
|
||||
createComponent,
|
||||
schema
|
||||
)
|
||||
const primary = draggableList.find(
|
||||
entry => entry.field === primaryDisplayColumnName
|
||||
)
|
||||
const sortable = draggableList.filter(
|
||||
entry => entry.field !== primaryDisplayColumnName
|
||||
)
|
||||
|
||||
return {
|
||||
primary,
|
||||
sortable,
|
||||
updateSortable: newDraggableList => {
|
||||
onChange(toGridFormat(newDraggableList.concat(primary)))
|
||||
},
|
||||
update: newEntry => {
|
||||
const newDraggableList = draggableList.map(entry => {
|
||||
return newEntry.field === entry.field ? newEntry : entry
|
||||
})
|
||||
|
||||
onChange(toGridFormat(newDraggableList))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default getColumns
|
|
@ -0,0 +1,374 @@
|
|||
import { it, expect, describe, beforeEach, vi } from "vitest"
|
||||
import getColumns from "./getColumns"
|
||||
|
||||
describe("getColumns", () => {
|
||||
beforeEach(ctx => {
|
||||
ctx.schema = {
|
||||
one: { name: "one", visible: false, order: 0, type: "foo" },
|
||||
two: { name: "two", visible: true, order: 1, type: "foo" },
|
||||
three: { name: "three", visible: true, order: 2, type: "foo" },
|
||||
four: { name: "four", visible: false, order: 3, type: "foo" },
|
||||
five: {
|
||||
name: "excluded",
|
||||
visible: true,
|
||||
order: 4,
|
||||
type: "foo",
|
||||
nestedJSON: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.primaryDisplayColumnName = "four"
|
||||
ctx.onChange = vi.fn()
|
||||
ctx.createComponent = (componentName, props) => {
|
||||
return { componentName, ...props }
|
||||
}
|
||||
})
|
||||
|
||||
describe("nested json fields", () => {
|
||||
beforeEach(ctx => {
|
||||
ctx.columns = getColumns({
|
||||
columns: null,
|
||||
schema: ctx.schema,
|
||||
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||
onChange: ctx.onChange,
|
||||
createComponent: ctx.createComponent,
|
||||
})
|
||||
})
|
||||
|
||||
it("does not return nested json fields, as the grid cannot display them", ctx => {
|
||||
expect(ctx.columns.sortable).not.toContainEqual({
|
||||
name: "excluded",
|
||||
visible: true,
|
||||
order: 4,
|
||||
type: "foo",
|
||||
nestedJSON: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("using the old grid column format", () => {
|
||||
beforeEach(ctx => {
|
||||
const oldGridFormatColumns = [
|
||||
{ displayName: "three label", name: "three" },
|
||||
{ displayName: "two label", name: "two" },
|
||||
]
|
||||
|
||||
ctx.columns = getColumns({
|
||||
columns: oldGridFormatColumns,
|
||||
schema: ctx.schema,
|
||||
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||
onChange: ctx.onChange,
|
||||
createComponent: ctx.createComponent,
|
||||
})
|
||||
})
|
||||
|
||||
it("returns the selected and unselected fields in the modern format, respecting the original order", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three label",
|
||||
},
|
||||
{
|
||||
_instanceName: "two",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two label",
|
||||
},
|
||||
{
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
},
|
||||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("default columns", () => {
|
||||
beforeEach(ctx => {
|
||||
ctx.columns = getColumns({
|
||||
columns: undefined,
|
||||
schema: ctx.schema,
|
||||
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||
onChange: ctx.onChange,
|
||||
createComponent: ctx.createComponent,
|
||||
})
|
||||
})
|
||||
|
||||
it("returns all columns, with non-hidden columns automatically selected", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_instanceName: "two",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two",
|
||||
},
|
||||
{
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three",
|
||||
},
|
||||
{
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
},
|
||||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
})
|
||||
})
|
||||
|
||||
it("Unselected columns should be placed at the end", ctx => {
|
||||
expect(ctx.columns.sortable[2].field).toEqual("one")
|
||||
})
|
||||
})
|
||||
|
||||
describe("missing columns", () => {
|
||||
beforeEach(ctx => {
|
||||
const gridFormatColumns = [
|
||||
{ label: "three label", field: "three", active: true },
|
||||
]
|
||||
|
||||
ctx.columns = getColumns({
|
||||
columns: gridFormatColumns,
|
||||
schema: ctx.schema,
|
||||
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||
onChange: ctx.onChange,
|
||||
createComponent: ctx.createComponent,
|
||||
})
|
||||
})
|
||||
|
||||
it("returns all columns, including those missing from the initial data", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three label",
|
||||
},
|
||||
{
|
||||
_instanceName: "two",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two",
|
||||
},
|
||||
{
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
},
|
||||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("invalid columns", () => {
|
||||
beforeEach(ctx => {
|
||||
const gridFormatColumns = [
|
||||
{ label: "three label", field: "three", active: true },
|
||||
{ label: "some nonsense", field: "some nonsense", active: true },
|
||||
]
|
||||
|
||||
ctx.columns = getColumns({
|
||||
columns: gridFormatColumns,
|
||||
schema: ctx.schema,
|
||||
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||
onChange: ctx.onChange,
|
||||
createComponent: ctx.createComponent,
|
||||
})
|
||||
})
|
||||
|
||||
it("returns all valid columns, excluding those that aren't valid for the schema", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three label",
|
||||
},
|
||||
{
|
||||
_instanceName: "two",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two",
|
||||
},
|
||||
{
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
},
|
||||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "four",
|
||||
label: "four",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("methods", () => {
|
||||
beforeEach(ctx => {
|
||||
const { update, updateSortable } = getColumns({
|
||||
columns: [],
|
||||
schema: ctx.schema,
|
||||
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||
onChange: ctx.onChange,
|
||||
createComponent: ctx.createComponent,
|
||||
})
|
||||
|
||||
ctx.update = update
|
||||
ctx.updateSortable = updateSortable
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
beforeEach(ctx => {
|
||||
ctx.update({
|
||||
field: "one",
|
||||
label: "a new label",
|
||||
active: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("calls the callback with the updated columns", ctx => {
|
||||
expect(ctx.onChange).toHaveBeenCalledTimes(1)
|
||||
expect(ctx.onChange).toHaveBeenCalledWith([
|
||||
{
|
||||
field: "two",
|
||||
label: "two",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
field: "three",
|
||||
label: "three",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
field: "one",
|
||||
label: "a new label",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
field: "four",
|
||||
label: "four",
|
||||
active: true,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateSortable", () => {
|
||||
beforeEach(ctx => {
|
||||
ctx.updateSortable([
|
||||
{
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "three",
|
||||
label: "three",
|
||||
},
|
||||
{
|
||||
_instanceName: "one",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "one",
|
||||
label: "one",
|
||||
},
|
||||
{
|
||||
_instanceName: "two",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
componentName: "@budibase/standard-components/labelfield",
|
||||
field: "two",
|
||||
label: "two",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("calls the callback with the updated columns", ctx => {
|
||||
expect(ctx.onChange).toHaveBeenCalledTimes(1)
|
||||
expect(ctx.onChange).toHaveBeenCalledWith([
|
||||
{
|
||||
field: "three",
|
||||
label: "three",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
field: "one",
|
||||
label: "one",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
field: "two",
|
||||
label: "two",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
field: "four",
|
||||
label: "four",
|
||||
active: true,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -2698,6 +2698,22 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"labelfield": {
|
||||
"name": "Text Field",
|
||||
"icon": "Text",
|
||||
"editable": true,
|
||||
"size": {
|
||||
"width": 400,
|
||||
"height": 32
|
||||
},
|
||||
"settings": [
|
||||
{
|
||||
"type": "plainText",
|
||||
"label": "Label",
|
||||
"key": "label"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stringfield": {
|
||||
"name": "Text Field",
|
||||
"icon": "Text",
|
||||
|
@ -6308,19 +6324,6 @@
|
|||
"key": "table",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "columns/grid",
|
||||
"label": "Columns",
|
||||
"key": "columns",
|
||||
"dependsOn": [
|
||||
"table",
|
||||
{
|
||||
"setting": "table.type",
|
||||
"value": "custom",
|
||||
"invert": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "filter",
|
||||
"label": "Filtering",
|
||||
|
@ -6417,6 +6420,18 @@
|
|||
"key": "stripeRows",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Columns",
|
||||
"settings": [
|
||||
{
|
||||
"type": "columns/grid",
|
||||
"key": "columns",
|
||||
"nested": true,
|
||||
"resetOn": "table"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Buttons",
|
||||
|
|
|
@ -19,6 +19,22 @@
|
|||
export let onRowClick = null
|
||||
export let buttons = null
|
||||
|
||||
// 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
|
||||
if (columns?.length && columns[0]?.active !== undefined) {
|
||||
return columns
|
||||
}
|
||||
|
||||
return columns?.map(column => ({
|
||||
label: column.displayName || column.name,
|
||||
field: column.name,
|
||||
active: true,
|
||||
}))
|
||||
}
|
||||
|
||||
$: parsedColumns = getParsedColumns(columns)
|
||||
|
||||
const context = getContext("context")
|
||||
const component = getContext("component")
|
||||
const {
|
||||
|
@ -33,16 +49,17 @@
|
|||
|
||||
let grid
|
||||
|
||||
$: columnWhitelist = columns?.map(col => col.name)
|
||||
$: schemaOverrides = getSchemaOverrides(columns)
|
||||
$: columnWhitelist = parsedColumns
|
||||
?.filter(col => col.active)
|
||||
?.map(col => col.field)
|
||||
$: schemaOverrides = getSchemaOverrides(parsedColumns)
|
||||
$: enrichedButtons = enrichButtons(buttons)
|
||||
|
||||
const getSchemaOverrides = columns => {
|
||||
let overrides = {}
|
||||
columns?.forEach(column => {
|
||||
overrides[column.name] = {
|
||||
displayName: column.displayName || column.name,
|
||||
visible: true,
|
||||
overrides[column.field] = {
|
||||
displayName: column.label,
|
||||
}
|
||||
})
|
||||
return overrides
|
||||
|
|
|
@ -55,11 +55,20 @@ export const deriveStores = context => {
|
|||
|
||||
// Apply whitelist if specified
|
||||
if ($columnWhitelist?.length) {
|
||||
Object.keys(enrichedSchema).forEach(key => {
|
||||
if (!$columnWhitelist.includes(key)) {
|
||||
delete enrichedSchema[key]
|
||||
const sortedColumns = {}
|
||||
|
||||
$columnWhitelist.forEach((columnKey, idx) => {
|
||||
const enrichedColumn = enrichedSchema[columnKey]
|
||||
if (enrichedColumn) {
|
||||
sortedColumns[columnKey] = {
|
||||
...enrichedColumn,
|
||||
order: idx,
|
||||
visible: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return sortedColumns
|
||||
}
|
||||
|
||||
return enrichedSchema
|
||||
|
|
Loading…
Reference in New Issue