Merge pull request #4638 from Budibase/feature/table-row-selection
Allow selection of rows from table component
This commit is contained in:
commit
ffe35bc5ec
16 changed files with 257 additions and 80 deletions
|
@ -47,7 +47,9 @@
|
||||||
<use xlink:href="#spectrum-css-icon-Dash100" />
|
<use xlink:href="#spectrum-css-icon-Dash100" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="spectrum-Checkbox-label">{text || ""}</span>
|
{#if text}
|
||||||
|
<span class="spectrum-Checkbox-label">{text}</span>
|
||||||
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -8,9 +8,21 @@
|
||||||
export let allowEditRows = false
|
export let allowEditRows = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if allowSelectRows}
|
<div>
|
||||||
<Checkbox value={selected} />
|
{#if allowSelectRows}
|
||||||
{/if}
|
<Checkbox value={selected} />
|
||||||
{#if allowEditRows}
|
{/if}
|
||||||
<ActionButton size="S" on:click={onEdit}>Edit</ActionButton>
|
{#if allowEditRows}
|
||||||
{/if}
|
<ActionButton size="S" on:click={onEdit}>Edit</ActionButton>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import SelectEditRenderer from "./SelectEditRenderer.svelte"
|
import SelectEditRenderer from "./SelectEditRenderer.svelte"
|
||||||
import { cloneDeep, deepGet } from "../helpers"
|
import { cloneDeep, deepGet } from "../helpers"
|
||||||
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
|
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
|
||||||
|
import Checkbox from "../Form/Checkbox.svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expected schema is our normal couch schemas for our tables.
|
* The expected schema is our normal couch schemas for our tables.
|
||||||
|
@ -31,7 +32,6 @@
|
||||||
export let allowEditRows = true
|
export let allowEditRows = true
|
||||||
export let allowEditColumns = true
|
export let allowEditColumns = true
|
||||||
export let selectedRows = []
|
export let selectedRows = []
|
||||||
export let editColumnTitle = "Edit"
|
|
||||||
export let customRenderers = []
|
export let customRenderers = []
|
||||||
export let disableSorting = false
|
export let disableSorting = false
|
||||||
export let autoSortColumns = true
|
export let autoSortColumns = true
|
||||||
|
@ -50,6 +50,8 @@
|
||||||
// Table state
|
// Table state
|
||||||
let height = 0
|
let height = 0
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
let checkboxStatus = false
|
||||||
|
|
||||||
$: schema = fixSchema(schema)
|
$: schema = fixSchema(schema)
|
||||||
$: if (!loading) loaded = true
|
$: if (!loading) loaded = true
|
||||||
$: fields = getFields(schema, showAutoColumns, autoSortColumns)
|
$: fields = getFields(schema, showAutoColumns, autoSortColumns)
|
||||||
|
@ -67,6 +69,16 @@
|
||||||
$: showEditColumn = allowEditRows || allowSelectRows
|
$: showEditColumn = allowEditRows || allowSelectRows
|
||||||
$: cellStyles = computeCellStyles(schema)
|
$: cellStyles = computeCellStyles(schema)
|
||||||
|
|
||||||
|
// Deselect the "select all" checkbox when the user navigates to a new page
|
||||||
|
$: {
|
||||||
|
let checkRowCount = rows.filter(o1 =>
|
||||||
|
selectedRows.some(o2 => o1._id === o2._id)
|
||||||
|
)
|
||||||
|
if (checkRowCount.length === 0) {
|
||||||
|
checkboxStatus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fixSchema = schema => {
|
const fixSchema = schema => {
|
||||||
let fixedSchema = {}
|
let fixedSchema = {}
|
||||||
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
|
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
|
||||||
|
@ -197,13 +209,32 @@
|
||||||
if (!allowSelectRows) {
|
if (!allowSelectRows) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (selectedRows.includes(row)) {
|
if (selectedRows.some(selectedRow => selectedRow._id === row._id)) {
|
||||||
selectedRows = selectedRows.filter(selectedRow => selectedRow !== row)
|
selectedRows = selectedRows.filter(
|
||||||
|
selectedRow => selectedRow._id !== row._id
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
selectedRows = [...selectedRows, row]
|
selectedRows = [...selectedRows, row]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleSelectAll = e => {
|
||||||
|
const select = !!e.detail
|
||||||
|
if (select) {
|
||||||
|
// Add any rows which are not already in selected rows
|
||||||
|
rows.forEach(row => {
|
||||||
|
if (selectedRows.findIndex(x => x._id === row._id) === -1) {
|
||||||
|
selectedRows.push(row)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Remove any rows from selected rows that are in the current data set
|
||||||
|
selectedRows = selectedRows.filter(el =>
|
||||||
|
rows.every(f => f._id !== el._id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const computeCellStyles = schema => {
|
const computeCellStyles = schema => {
|
||||||
let styles = {}
|
let styles = {}
|
||||||
Object.keys(schema || {}).forEach(field => {
|
Object.keys(schema || {}).forEach(field => {
|
||||||
|
@ -244,7 +275,14 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-Table-headCell spectrum-Table-headCell--divider spectrum-Table-headCell--edit"
|
class="spectrum-Table-headCell spectrum-Table-headCell--divider spectrum-Table-headCell--edit"
|
||||||
>
|
>
|
||||||
{editColumnTitle || ""}
|
{#if allowSelectRows}
|
||||||
|
<Checkbox
|
||||||
|
bind:value={checkboxStatus}
|
||||||
|
on:change={toggleSelectAll}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
Edit
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each fields as field}
|
{#each fields as field}
|
||||||
|
@ -302,11 +340,16 @@
|
||||||
{#if showEditColumn}
|
{#if showEditColumn}
|
||||||
<div
|
<div
|
||||||
class="spectrum-Table-cell spectrum-Table-cell--divider spectrum-Table-cell--edit"
|
class="spectrum-Table-cell spectrum-Table-cell--divider spectrum-Table-cell--edit"
|
||||||
|
on:click={e => {
|
||||||
|
toggleSelectRow(row)
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectEditRenderer
|
<SelectEditRenderer
|
||||||
data={row}
|
data={row}
|
||||||
selected={selectedRows.includes(row)}
|
selected={selectedRows.findIndex(
|
||||||
onToggleSelection={() => toggleSelectRow(row)}
|
selectedRow => selectedRow._id === row._id
|
||||||
|
) !== -1}
|
||||||
onEdit={e => editRow(e, row)}
|
onEdit={e => editRow(e, row)}
|
||||||
{allowSelectRows}
|
{allowSelectRows}
|
||||||
{allowEditRows}
|
{allowEditRows}
|
||||||
|
|
|
@ -53,10 +53,10 @@
|
||||||
to-gfm-code-block "^0.1.1"
|
to-gfm-code-block "^0.1.1"
|
||||||
year "^0.2.1"
|
year "^0.2.1"
|
||||||
|
|
||||||
"@budibase/string-templates@^1.0.66-alpha.0":
|
"@budibase/string-templates@^1.0.72-alpha.0":
|
||||||
version "1.0.72"
|
version "1.0.75"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.72.tgz#acc154e402cce98ea30eedde9c6124183ee9b37c"
|
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.75.tgz#5b4061f1a626160ec092f32f036541376298100c"
|
||||||
integrity sha512-w715TjgO6NUHkZNqoOEo8lAKJ/PQ4b00ATWSX5VB523SAu7y/uOiqKqV1E3fgwxq1o8L+Ff7rn9FTkiYtjkV/g==
|
integrity sha512-hPgr6n5cpSCGFEha5DS/P+rtRXOLc72M6y4J/scl59JvUi/ZUJkjRgJdpQPdBLu04CNKp89V59+rAqAuDjOC0g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/handlebars-helpers" "^0.11.7"
|
"@budibase/handlebars-helpers" "^0.11.7"
|
||||||
dayjs "^1.10.4"
|
dayjs "^1.10.4"
|
||||||
|
|
|
@ -27,10 +27,13 @@ filterTests(["smoke", "all"], () => {
|
||||||
it("updates a column on the table", () => {
|
it("updates a column on the table", () => {
|
||||||
cy.get(".title").click()
|
cy.get(".title").click()
|
||||||
cy.get(".spectrum-Table-editIcon > use").click()
|
cy.get(".spectrum-Table-editIcon > use").click()
|
||||||
cy.get("input").eq(1).type("updated", { force: true })
|
cy.get(".modal-inner-wrapper").within(() => {
|
||||||
|
|
||||||
|
cy.get("input").eq(0).type("updated", { force: true })
|
||||||
// Unset table display column
|
// Unset table display column
|
||||||
cy.get(".spectrum-Switch-input").eq(1).click()
|
cy.get(".spectrum-Switch-input").eq(1).click()
|
||||||
cy.contains("Save Column").click()
|
cy.contains("Save Column").click()
|
||||||
|
})
|
||||||
cy.contains("nameupdated ").should("contain", "nameupdated")
|
cy.contains("nameupdated ").should("contain", "nameupdated")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -172,17 +172,19 @@ Cypress.Commands.add("addRow", values => {
|
||||||
|
|
||||||
Cypress.Commands.add("addRowMultiValue", values => {
|
Cypress.Commands.add("addRowMultiValue", values => {
|
||||||
cy.contains("Create row").click()
|
cy.contains("Create row").click()
|
||||||
cy.get(".spectrum-Form-itemField")
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
.click()
|
cy.get(".spectrum-Form-itemField")
|
||||||
.then(() => {
|
.click()
|
||||||
cy.get(".spectrum-Popover").within(() => {
|
.then(() => {
|
||||||
for (let i = 0; i < values.length; i++) {
|
cy.get(".spectrum-Popover").within(() => {
|
||||||
cy.get(".spectrum-Menu-item").eq(i).click()
|
for (let i = 0; i < values.length; i++) {
|
||||||
}
|
cy.get(".spectrum-Menu-item").eq(i).click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cy.get(".spectrum-Dialog-grid").click("top")
|
||||||
|
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").click("top")
|
})
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createUser", email => {
|
Cypress.Commands.add("createUser", email => {
|
||||||
|
|
|
@ -32,12 +32,14 @@ export const getBindableProperties = (asset, componentId) => {
|
||||||
const urlBindings = getUrlBindings(asset)
|
const urlBindings = getUrlBindings(asset)
|
||||||
const deviceBindings = getDeviceBindings()
|
const deviceBindings = getDeviceBindings()
|
||||||
const stateBindings = getStateBindings()
|
const stateBindings = getStateBindings()
|
||||||
|
const selectedRowsBindings = getSelectedRowsBindings(asset)
|
||||||
return [
|
return [
|
||||||
...contextBindings,
|
...contextBindings,
|
||||||
...urlBindings,
|
...urlBindings,
|
||||||
...stateBindings,
|
...stateBindings,
|
||||||
...userBindings,
|
...userBindings,
|
||||||
...deviceBindings,
|
...deviceBindings,
|
||||||
|
...selectedRowsBindings,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +317,40 @@ const getDeviceBindings = () => {
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all selected rows bindings for tables in the current asset.
|
||||||
|
*/
|
||||||
|
const getSelectedRowsBindings = asset => {
|
||||||
|
let bindings = []
|
||||||
|
if (get(store).clientFeatures?.rowSelection) {
|
||||||
|
// Add bindings for table components
|
||||||
|
let tables = findAllMatchingComponents(asset?.props, component =>
|
||||||
|
component._component.endsWith("table")
|
||||||
|
)
|
||||||
|
const safeState = makePropSafe("rowSelection")
|
||||||
|
bindings = bindings.concat(
|
||||||
|
tables.map(table => ({
|
||||||
|
type: "context",
|
||||||
|
runtimeBinding: `${safeState}.${makePropSafe(table._id)}`,
|
||||||
|
readableBinding: `${table._instanceName}.Selected rows`,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add bindings for table blocks
|
||||||
|
let tableBlocks = findAllMatchingComponents(asset?.props, component =>
|
||||||
|
component._component.endsWith("tableblock")
|
||||||
|
)
|
||||||
|
bindings = bindings.concat(
|
||||||
|
tableBlocks.map(block => ({
|
||||||
|
type: "context",
|
||||||
|
runtimeBinding: `${safeState}.${makePropSafe(block._id + "-table")}`,
|
||||||
|
readableBinding: `${block._instanceName}.Selected rows`,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return bindings
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all state bindings that are globally available.
|
* Gets all state bindings that are globally available.
|
||||||
*/
|
*/
|
||||||
|
@ -597,14 +633,9 @@ const buildFormSchema = component => {
|
||||||
* in the app.
|
* in the app.
|
||||||
*/
|
*/
|
||||||
export const getAllStateVariables = () => {
|
export const getAllStateVariables = () => {
|
||||||
// Get all component containing assets
|
|
||||||
let allAssets = []
|
|
||||||
allAssets = allAssets.concat(get(store).layouts || [])
|
|
||||||
allAssets = allAssets.concat(get(store).screens || [])
|
|
||||||
|
|
||||||
// Find all button action settings in all components
|
// Find all button action settings in all components
|
||||||
let eventSettings = []
|
let eventSettings = []
|
||||||
allAssets.forEach(asset => {
|
getAllAssets().forEach(asset => {
|
||||||
findAllMatchingComponents(asset.props, component => {
|
findAllMatchingComponents(asset.props, component => {
|
||||||
const settings = getComponentSettings(component._component)
|
const settings = getComponentSettings(component._component)
|
||||||
settings
|
settings
|
||||||
|
@ -635,6 +666,15 @@ export const getAllStateVariables = () => {
|
||||||
return Array.from(bindingSet)
|
return Array.from(bindingSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAllAssets = () => {
|
||||||
|
// Get all component containing assets
|
||||||
|
let allAssets = []
|
||||||
|
allAssets = allAssets.concat(get(store).layouts || [])
|
||||||
|
allAssets = allAssets.concat(get(store).screens || [])
|
||||||
|
|
||||||
|
return allAssets
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recurses the input object to remove any instances of bindings.
|
* Recurses the input object to remove any instances of bindings.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -41,6 +41,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
intelligentLoading: false,
|
intelligentLoading: false,
|
||||||
deviceAwareness: false,
|
deviceAwareness: false,
|
||||||
state: false,
|
state: false,
|
||||||
|
rowSelection: false,
|
||||||
customThemes: false,
|
customThemes: false,
|
||||||
devicePreview: false,
|
devicePreview: false,
|
||||||
messagePassing: false,
|
messagePassing: false,
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"state": true,
|
"state": true,
|
||||||
"customThemes": true,
|
"customThemes": true,
|
||||||
"devicePreview": true,
|
"devicePreview": true,
|
||||||
"messagePassing": true
|
"messagePassing": true,
|
||||||
|
"rowSelection": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
@ -2714,6 +2715,13 @@
|
||||||
"key": "showAutoColumns",
|
"key": "showAutoColumns",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Allow row selection",
|
||||||
|
"key": "allowSelectRows",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Link table rows",
|
"label": "Link table rows",
|
||||||
|
@ -2973,6 +2981,11 @@
|
||||||
"label": "Show auto columns",
|
"label": "Show auto columns",
|
||||||
"key": "showAutoColumns"
|
"key": "showAutoColumns"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Allow row selection",
|
||||||
|
"key": "allowSelectRows"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Link table rows",
|
"label": "Link table rows",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
|
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
|
||||||
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
|
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
|
||||||
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
|
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
|
||||||
|
import RowSelectionProvider from "components/context/RowSelectionProvider.svelte"
|
||||||
import SettingsBar from "components/preview/SettingsBar.svelte"
|
import SettingsBar from "components/preview/SettingsBar.svelte"
|
||||||
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
|
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
|
||||||
import HoverIndicator from "components/preview/HoverIndicator.svelte"
|
import HoverIndicator from "components/preview/HoverIndicator.svelte"
|
||||||
|
@ -90,59 +91,61 @@
|
||||||
<UserBindingsProvider>
|
<UserBindingsProvider>
|
||||||
<DeviceBindingsProvider>
|
<DeviceBindingsProvider>
|
||||||
<StateBindingsProvider>
|
<StateBindingsProvider>
|
||||||
<!-- Settings bar can be rendered outside of device preview -->
|
<RowSelectionProvider>
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
<!-- Settings bar can be rendered outside of device preview -->
|
||||||
{#key $builderStore.selectedComponentId}
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
{#if $builderStore.inBuilder}
|
{#key $builderStore.selectedComponentId}
|
||||||
<SettingsBar />
|
{#if $builderStore.inBuilder}
|
||||||
{/if}
|
<SettingsBar />
|
||||||
{/key}
|
{/if}
|
||||||
|
{/key}
|
||||||
|
|
||||||
<!-- Clip boundary for selection indicators -->
|
<!-- Clip boundary for selection indicators -->
|
||||||
<div
|
<div
|
||||||
id="clip-root"
|
id="clip-root"
|
||||||
class:preview={$builderStore.inBuilder}
|
class:preview={$builderStore.inBuilder}
|
||||||
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
||||||
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
||||||
>
|
>
|
||||||
<!-- Actual app -->
|
<!-- Actual app -->
|
||||||
<div id="app-root">
|
<div id="app-root">
|
||||||
<CustomThemeWrapper>
|
<CustomThemeWrapper>
|
||||||
{#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`}
|
{#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`}
|
||||||
<Component
|
<Component
|
||||||
isLayout
|
isLayout
|
||||||
instance={$screenStore.activeLayout.props}
|
instance={$screenStore.activeLayout.props}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Flatpickr needs to be inside the theme wrapper.
|
Flatpickr needs to be inside the theme wrapper.
|
||||||
It also needs its own container because otherwise it hijacks
|
It also needs its own container because otherwise it hijacks
|
||||||
key events on the whole page. It is painful to work with.
|
key events on the whole page. It is painful to work with.
|
||||||
-->
|
-->
|
||||||
<div id="flatpickr-root" />
|
<div id="flatpickr-root" />
|
||||||
|
|
||||||
<!-- Modal container to ensure they sit on top -->
|
<!-- Modal container to ensure they sit on top -->
|
||||||
<div class="modal-container" />
|
<div class="modal-container" />
|
||||||
|
|
||||||
<!-- Layers on top of app -->
|
<!-- Layers on top of app -->
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
<ConfirmationDisplay />
|
<ConfirmationDisplay />
|
||||||
<PeekScreenDisplay />
|
<PeekScreenDisplay />
|
||||||
</CustomThemeWrapper>
|
</CustomThemeWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Selection indicators should be bounded by device -->
|
<!-- Selection indicators should be bounded by device -->
|
||||||
<!--
|
<!--
|
||||||
We don't want to key these by componentID as they control their own
|
We don't want to key these by componentID as they control their own
|
||||||
re-mounting to avoid flashes.
|
re-mounting to avoid flashes.
|
||||||
-->
|
-->
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
<SelectionIndicator />
|
<SelectionIndicator />
|
||||||
<HoverIndicator />
|
<HoverIndicator />
|
||||||
<DNDHandler />
|
<DNDHandler />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</RowSelectionProvider>
|
||||||
</StateBindingsProvider>
|
</StateBindingsProvider>
|
||||||
</DeviceBindingsProvider>
|
</DeviceBindingsProvider>
|
||||||
</UserBindingsProvider>
|
</UserBindingsProvider>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let quiet
|
export let quiet
|
||||||
export let compact
|
export let compact
|
||||||
export let size
|
export let size
|
||||||
|
export let allowSelectRows
|
||||||
export let linkRows
|
export let linkRows
|
||||||
export let linkURL
|
export let linkURL
|
||||||
export let linkColumn
|
export let linkColumn
|
||||||
|
@ -157,6 +158,7 @@
|
||||||
>
|
>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="table"
|
type="table"
|
||||||
|
context="table"
|
||||||
props={{
|
props={{
|
||||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||||
columns: tableColumns,
|
columns: tableColumns,
|
||||||
|
@ -164,6 +166,7 @@
|
||||||
rowCount,
|
rowCount,
|
||||||
quiet,
|
quiet,
|
||||||
compact,
|
compact,
|
||||||
|
allowSelectRows,
|
||||||
size,
|
size,
|
||||||
linkRows,
|
linkRows,
|
||||||
linkURL,
|
linkURL,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { Table } from "@budibase/bbui"
|
import { Table } from "@budibase/bbui"
|
||||||
import SlotRenderer from "./SlotRenderer.svelte"
|
import SlotRenderer from "./SlotRenderer.svelte"
|
||||||
import { UnsortableTypes } from "../../../constants"
|
import { UnsortableTypes } from "../../../constants"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let dataProvider
|
export let dataProvider
|
||||||
export let columns
|
export let columns
|
||||||
|
@ -14,10 +15,12 @@
|
||||||
export let linkURL
|
export let linkURL
|
||||||
export let linkColumn
|
export let linkColumn
|
||||||
export let linkPeek
|
export let linkPeek
|
||||||
|
export let allowSelectRows
|
||||||
export let compact
|
export let compact
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, getAction, ActionTypes, routeStore } = getContext("sdk")
|
const { styleable, getAction, ActionTypes, routeStore, rowSelectionStore } =
|
||||||
|
getContext("sdk")
|
||||||
const customColumnKey = `custom-${Math.random()}`
|
const customColumnKey = `custom-${Math.random()}`
|
||||||
const customRenderers = [
|
const customRenderers = [
|
||||||
{
|
{
|
||||||
|
@ -25,7 +28,7 @@
|
||||||
component: SlotRenderer,
|
component: SlotRenderer,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
let selectedRows = []
|
||||||
$: hasChildren = $component.children
|
$: hasChildren = $component.children
|
||||||
$: loading = dataProvider?.loading ?? false
|
$: loading = dataProvider?.loading ?? false
|
||||||
$: data = dataProvider?.rows || []
|
$: data = dataProvider?.rows || []
|
||||||
|
@ -36,6 +39,12 @@
|
||||||
dataProvider?.id,
|
dataProvider?.id,
|
||||||
ActionTypes.SetDataProviderSorting
|
ActionTypes.SetDataProviderSorting
|
||||||
)
|
)
|
||||||
|
$: {
|
||||||
|
rowSelectionStore.actions.updateSelection(
|
||||||
|
$component.id,
|
||||||
|
selectedRows.map(row => row._id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const getFields = (schema, customColumns, showAutoColumns) => {
|
const getFields = (schema, customColumns, showAutoColumns) => {
|
||||||
// Check for an invalid column selection
|
// Check for an invalid column selection
|
||||||
|
@ -117,6 +126,10 @@
|
||||||
const split = linkURL.split("/:")
|
const split = linkURL.split("/:")
|
||||||
routeStore.actions.navigate(`${split[0]}/${id}`, linkPeek)
|
routeStore.actions.navigate(`${split[0]}/${id}`, linkPeek)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
rowSelectionStore.actions.updateSelection($component.id, [])
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={$component.styles} class={size}>
|
<div use:styleable={$component.styles} class={size}>
|
||||||
|
@ -128,7 +141,8 @@
|
||||||
{quiet}
|
{quiet}
|
||||||
{compact}
|
{compact}
|
||||||
{customRenderers}
|
{customRenderers}
|
||||||
allowSelectRows={false}
|
allowSelectRows={!!allowSelectRows}
|
||||||
|
bind:selectedRows
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
showAutoColumns={true}
|
showAutoColumns={true}
|
||||||
|
@ -139,10 +153,19 @@
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</Table>
|
</Table>
|
||||||
|
{#if allowSelectRows && selectedRows.length}
|
||||||
|
<div class="row-count">
|
||||||
|
{selectedRows.length} row{selectedRows.length === 1 ? "" : "s"} selected
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
background-color: var(--spectrum-alias-background-color-secondary);
|
background-color: var(--spectrum-alias-background-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row-count {
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
import Provider from "./Provider.svelte"
|
||||||
|
import { rowSelectionStore } from "stores"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Provider key="rowSelection" data={$rowSelectionStore}>
|
||||||
|
<slot />
|
||||||
|
</Provider>
|
|
@ -6,6 +6,7 @@ import {
|
||||||
screenStore,
|
screenStore,
|
||||||
builderStore,
|
builderStore,
|
||||||
uploadStore,
|
uploadStore,
|
||||||
|
rowSelectionStore,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import { styleable } from "utils/styleable"
|
import { styleable } from "utils/styleable"
|
||||||
import { linkable } from "utils/linkable"
|
import { linkable } from "utils/linkable"
|
||||||
|
@ -19,6 +20,7 @@ export default {
|
||||||
authStore,
|
authStore,
|
||||||
notificationStore,
|
notificationStore,
|
||||||
routeStore,
|
routeStore,
|
||||||
|
rowSelectionStore,
|
||||||
screenStore,
|
screenStore,
|
||||||
builderStore,
|
builderStore,
|
||||||
uploadStore,
|
uploadStore,
|
||||||
|
|
|
@ -10,7 +10,7 @@ export { peekStore } from "./peek"
|
||||||
export { stateStore } from "./state"
|
export { stateStore } from "./state"
|
||||||
export { themeStore } from "./theme"
|
export { themeStore } from "./theme"
|
||||||
export { uploadStore } from "./uploads.js"
|
export { uploadStore } from "./uploads.js"
|
||||||
|
export { rowSelectionStore } from "./rowSelection.js"
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
|
||||||
|
|
22
packages/client/src/stores/rowSelection.js
Normal file
22
packages/client/src/stores/rowSelection.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
const createRowSelectionStore = () => {
|
||||||
|
const store = writable({})
|
||||||
|
|
||||||
|
function updateSelection(componentId, selectedRows) {
|
||||||
|
store.update(state => {
|
||||||
|
state[componentId] = [...selectedRows]
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
set: store.set,
|
||||||
|
actions: {
|
||||||
|
updateSelection,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rowSelectionStore = createRowSelectionStore()
|
Loading…
Reference in a new issue