Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selected row + export data support for grid blocks (new table component) #13426

Merged
merged 19 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e1a9762
Always allow selecting rows in grids in apps, and add binding for gri…
aptkingston Apr 8, 2024
53bbaac
Update export data action to work with new table component
aptkingston Apr 8, 2024
8e7e2dd
Clarify wording
aptkingston Apr 8, 2024
cfcda49
Fix data export for legacy configs
aptkingston Apr 8, 2024
1ec9acc
Revert unnecessary changes
aptkingston Apr 8, 2024
24d6bfc
Restore commented out code
aptkingston Apr 8, 2024
09ac326
Merge branch 'master' into new-table-selected-rows
aptkingston Apr 8, 2024
fe93f25
Merge branch 'master' into new-table-selected-rows
aptkingston Apr 9, 2024
34b1399
Merge branch 'master' into new-table-selected-rows
aptkingston Apr 10, 2024
e21a979
Merge branch 'master' into new-table-selected-rows
aptkingston Apr 16, 2024
22c1c57
Merge branch 'master' into new-table-selected-rows
aptkingston Apr 17, 2024
0c24b7b
Merge branch 'master' into new-table-selected-rows
ConorWebb96 Apr 18, 2024
ba060c6
Merge branch 'master' into new-table-selected-rows
aptkingston Apr 26, 2024
3d01bd3
Merge branch 'master' of github.com:Budibase/budibase into new-table-…
aptkingston May 13, 2024
4516995
Remove leftover repeat param in grids
aptkingston May 13, 2024
9a0f747
Merge branch 'new-table-selected-rows' of github.com:Budibase/budibas…
aptkingston May 13, 2024
a5f627e
Move grid block provider top level since nesting doesn't matter
aptkingston May 13, 2024
303a726
Ensure rows can always be selected in grids in apps
aptkingston May 13, 2024
8960b1b
Merge branch 'master' into new-table-selected-rows
aptkingston May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,20 @@
},
]

$: tables = findAllMatchingComponents($selectedScreen?.props, component =>
component._component.endsWith("table")
)
$: tableBlocks = findAllMatchingComponents(
$: components = findAllMatchingComponents(
$selectedScreen?.props,
component => component._component.endsWith("tableblock")
component => {
const type = component._component
return (
type.endsWith("/table") ||
type.endsWith("/tableblock") ||
type.endsWith("/gridblock")
)
}
)
$: components = tables.concat(tableBlocks)
$: componentOptions = components.map(table => ({
label: table._instanceName,
value: table._component.includes("tableblock")
value: table._component.endsWith("/tableblock")
? `${table._id}-table`
: table._id,
}))
Expand All @@ -69,6 +72,7 @@
$: selectedTable = components.find(
component => component._id === selectedTableId
)
$: parameters.rows = `{{ literal [${parameters.tableComponentId}].[selectedRows] }}`

onMount(() => {
if (!parameters.type) {
Expand Down
20 changes: 16 additions & 4 deletions packages/client/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7017,10 +7017,22 @@
]
}
],
"context": {
"type": "schema",
"scope": "local"
},
"context": [
{
"type": "schema",
"scope": "local"
},
{
"type": "static",
"values": [
{
"label": "Selected rows",
"key": "selectedRows",
"type": "array"
}
]
}
],
"actions": ["RefreshDatasource"]
},
"bbreferencefield": {
Expand Down
101 changes: 62 additions & 39 deletions packages/client/src/components/app/GridBlock.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script>
// NOTE: this is not a block - it's just named as such to avoid confusing users,
// because it functions similarly to one
import { getContext } from "svelte"
import { get } from "svelte/store"
import { getContext, onMount } from "svelte"
import { get, derived, readable } from "svelte/store"
import { Grid } from "@budibase/frontend-core"

// table is actually any datasource, but called table for legacy compatibility
Expand All @@ -19,7 +19,6 @@
export let columns = null
export let onRowClick = null
export let buttons = null
export let repeat = null

const context = getContext("context")
const component = getContext("component")
Expand All @@ -36,17 +35,18 @@
} = getContext("sdk")

let grid
let gridContext

$: columnWhitelist = parsedColumns
?.filter(col => col.active)
?.map(col => col.field)
$: parsedColumns = getParsedColumns(columns)
$: columnWhitelist = parsedColumns.filter(x => x.active).map(x => x.field)
$: schemaOverrides = getSchemaOverrides(parsedColumns)
$: enrichedButtons = enrichButtons(buttons)
$: parsedColumns = getParsedColumns(columns)
$: selectedRows = deriveSelectedRows(gridContext)
$: data = { selectedRows: $selectedRows }
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => grid?.getContext()?.rows.actions.refreshData(),
callback: () => gridContext?.rows.actions.refreshData(),
metadata: { dataSource: table },
},
]
Expand All @@ -68,12 +68,14 @@

// Parses columns to fix older formats
const getParsedColumns = columns => {
if (!columns?.length) {
return []
}
// If the first element has an active key all elements should be in the new format
if (columns?.length && columns[0]?.active !== undefined) {
if (columns[0].active !== undefined) {
return columns
}

return columns?.map(column => ({
return columns.map(column => ({
label: column.displayName || column.name,
field: column.name,
active: true,
Expand All @@ -82,7 +84,7 @@

const getSchemaOverrides = columns => {
let overrides = {}
columns?.forEach(column => {
columns.forEach(column => {
overrides[column.field] = {
displayName: column.label,
}
Expand All @@ -109,6 +111,23 @@
}))
}

const deriveSelectedRows = gridContext => {
if (!gridContext) {
return readable([])
}
return derived(
[gridContext.selectedRows, gridContext.rowLookupMap, gridContext.rows],
([$selectedRows, $rowLookupMap, $rows]) => {
return Object.entries($selectedRows || {})
.filter(([_, selected]) => selected)
.map(([rowId]) => {
const idx = $rowLookupMap[rowId]
return gridContext.rows.actions.cleanRow($rows[idx])
})
}
)
}

const getSanitisedStyles = styles => {
return {
...styles,
Expand All @@ -118,40 +137,44 @@
},
}
}

onMount(() => {
gridContext = grid.getContext()
})
</script>

<div use:styleable={styles} class:in-builder={$builderStore.inBuilder}>
<span style="--height:{height};">
<Provider {actions}>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}
{quiet}
{initialFilter}
{initialSortColumn}
{initialSortOrder}
{fixedRowHeight}
{columnWhitelist}
{schemaOverrides}
{repeat}
canAddRows={allowAddRows}
canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows}
canEditColumns={false}
canExpandRows={false}
canSaveSchema={false}
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</Provider>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}
{quiet}
{initialFilter}
{initialSortColumn}
{initialSortOrder}
{fixedRowHeight}
{columnWhitelist}
{schemaOverrides}
canAddRows={allowAddRows}
canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows}
canEditColumns={false}
canExpandRows={false}
canSaveSchema={false}
canSelectRows={true}
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</span>
</div>

<Provider {data} {actions} />

<style>
div {
display: flex;
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/stores/rowSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const createRowSelectionStore = () => {
const componentId = Object.keys(selection).find(
componentId => componentId === tableComponentId
)
return selection[componentId] || {}
return selection[componentId]
}

return {
Expand Down
64 changes: 46 additions & 18 deletions packages/client/src/utils/buttonActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,31 +333,59 @@ const s3UploadHandler = async action => {
}
}

/**
* For new configs, "rows" is defined and enriched to be the array of rows to
* export. For old configs it will be undefined and we need to use the legacy
* row selection store in combination with the tableComponentId parameter.
*/
const exportDataHandler = async action => {
let selection = rowSelectionStore.actions.getSelection(
action.parameters.tableComponentId
)
if (selection.selectedRows && selection.selectedRows.length > 0) {
let { tableComponentId, rows, type, columns, delimiter, customHeaders } =
action.parameters
let tableId

// Handle legacy configs using the row selection store
if (!rows?.length) {
const selection = rowSelectionStore.actions.getSelection(tableComponentId)
if (selection?.selectedRows?.length) {
rows = selection.selectedRows
tableId = selection.tableId
}
}

// Get table ID from first row if needed
if (!tableId) {
tableId = rows?.[0]?.tableId
}

// Handle no rows selected
if (!rows?.length) {
notificationStore.actions.error("Please select at least one row")
}
// Handle case where we're not using a DS+
else if (!tableId) {
notificationStore.actions.error(
"You can only export data from table datasources"
)
}
// Happy path when we have both rows and table ID
else {
try {
// Flatten rows if required
if (typeof rows[0] !== "string") {
rows = rows.map(row => row._id)
}
const data = await API.exportRows({
tableId: selection.tableId,
rows: selection.selectedRows,
format: action.parameters.type,
columns: action.parameters.columns?.map(
column => column.name || column
),
delimiter: action.parameters.delimiter,
customHeaders: action.parameters.customHeaders,
tableId,
rows,
format: type,
columns: columns?.map(column => column.name || column),
delimiter,
customHeaders,
})
download(
new Blob([data], { type: "text/plain" }),
`${selection.tableId}.${action.parameters.type}`
)
download(new Blob([data], { type: "text/plain" }), `${tableId}.${type}`)
} catch (error) {
notificationStore.actions.error("There was an error exporting the data")
}
} else {
notificationStore.actions.error("Please select at least one row")
}
}

Expand Down
20 changes: 7 additions & 13 deletions packages/frontend-core/src/components/grid/cells/GutterCell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
const { config, dispatch, selectedRows } = getContext("grid")
const svelteDispatch = createEventDispatcher()

$: selectionEnabled = $config.canSelectRows || $config.canDeleteRows

const select = e => {
e.stopPropagation()
svelteDispatch("select")
Expand Down Expand Up @@ -52,15 +54,15 @@
<div
on:click={select}
class="checkbox"
class:visible={$config.canDeleteRows &&
class:visible={selectionEnabled &&
(disableNumber || rowSelected || rowHovered || rowFocused)}
>
<Checkbox value={rowSelected} {disabled} />
</div>
{#if !disableNumber}
<div
class="number"
class:visible={!$config.canDeleteRows ||
class:visible={!selectionEnabled ||
!(rowSelected || rowHovered || rowFocused)}
>
{row.__idx + 1}
Expand Down Expand Up @@ -117,19 +119,11 @@
.expand {
margin-right: 4px;
}
.expand {
.expand:not(.visible),
.expand:not(.visible) :global(*) {
opacity: 0;
pointer-events: none !important;
}
.expand :global(.spectrum-Icon) {
pointer-events: none;
}
.expand.visible {
opacity: 1;
}
.expand.visible :global(.spectrum-Icon) {
pointer-events: all;
}

.delete:hover {
cursor: pointer;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend-core/src/components/grid/layout/Grid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
export let canDeleteRows = true
export let canEditColumns = true
export let canSaveSchema = true
export let canSelectRows = false
export let stripeRows = false
export let quiet = false
export let collaboration = true
Expand Down Expand Up @@ -94,6 +95,7 @@
canDeleteRows,
canEditColumns,
canSaveSchema,
canSelectRows,
stripeRows,
quiet,
collaboration,
Expand Down
3 changes: 1 addition & 2 deletions packages/frontend-core/src/components/grid/stores/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,11 @@ export const deriveStores = context => {
}

export const createActions = context => {
const { focusedCellId, selectedRows, hoveredRowId } = context
const { focusedCellId, hoveredRowId } = context

// Callback when leaving the grid, deselecting all focussed or selected items
const blur = () => {
focusedCellId.set(null)
selectedRows.set({})
hoveredRowId.set(null)
}

Expand Down