add row selection functionality to tables

This commit is contained in:
Peter Clement 2022-02-11 11:55:35 +00:00
parent 0829723d99
commit 350edc2aec
11 changed files with 153 additions and 54 deletions

View File

@ -3,6 +3,7 @@
import "@spectrum-css/table/dist/index-vars.css" import "@spectrum-css/table/dist/index-vars.css"
import CellRenderer from "./CellRenderer.svelte" import CellRenderer from "./CellRenderer.svelte"
import SelectEditRenderer from "./SelectEditRenderer.svelte" import SelectEditRenderer from "./SelectEditRenderer.svelte"
import Checkbox from "../Form/Checkbox.svelte"
import { cloneDeep } from "lodash" import { cloneDeep } from "lodash"
import { deepGet } from "../utils/helpers" import { deepGet } from "../utils/helpers"
@ -29,7 +30,7 @@
export let editColumnTitle = "Edit" export let editColumnTitle = "Edit"
export let customRenderers = [] export let customRenderers = []
export let disableSorting = false export let disableSorting = false
export let allowSelectAllRows = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
// Config // Config
@ -204,12 +205,27 @@
dispatch("editrow", cloneDeep(row)) dispatch("editrow", cloneDeep(row))
} }
const toggleSelectAll = e => {
if (!allowSelectAllRows) {
return
}
if (e.detail) {
selectedRows = rows
} else {
selectedRows = []
}
}
const toggleSelectRow = row => { const toggleSelectRow = row => {
if (!allowSelectRows) { if (!allowSelectRows) {
return return
} }
if (selectedRows.includes(row)) { if (
selectedRows = selectedRows.filter(selectedRow => selectedRow !== row) selectedRows.findIndex(selectedRow => selectedRow._id === row._id) === 0
) {
selectedRows = selectedRows.filter(
selectedRow => selectedRow._id !== row._id
)
} else { } else {
selectedRows = [...selectedRows, row] selectedRows = [...selectedRows, row]
} }
@ -234,7 +250,11 @@
{#if showEditColumn} {#if showEditColumn}
<th class="spectrum-Table-headCell"> <th class="spectrum-Table-headCell">
<div class="spectrum-Table-headCell-content"> <div class="spectrum-Table-headCell-content">
{editColumnTitle || ""} {#if allowSelectAllRows}
<Checkbox on:change={toggleSelectAll} />
{:else}
{editColumnTitle || ""}
{/if}
</div> </div>
</th> </th>
{/if} {/if}
@ -299,7 +319,9 @@
<div class="spectrum-Table-cell-content"> <div class="spectrum-Table-cell-content">
<SelectEditRenderer <SelectEditRenderer
data={row} data={row}
selected={selectedRows.includes(row)} selected={selectedRows.findIndex(
selectedRow => selectedRow._id === row._id
) !== -1}
onToggleSelection={() => toggleSelectRow(row)} onToggleSelection={() => toggleSelectRow(row)}
onEdit={e => editRow(e, row)} onEdit={e => editRow(e, row)}
{allowSelectRows} {allowSelectRows}
@ -364,6 +386,7 @@
overflow: hidden; overflow: hidden;
position: relative; position: relative;
z-index: 0; z-index: 0;
cursor: pointer;
} }
.container { .container {

View File

@ -35,12 +35,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 rowBindings = getRowBindings()
return [ return [
...contextBindings, ...contextBindings,
...urlBindings, ...urlBindings,
...stateBindings, ...stateBindings,
...userBindings, ...userBindings,
...deviceBindings, ...deviceBindings,
...rowBindings,
] ]
} }
@ -321,13 +323,33 @@ const getDeviceBindings = () => {
return bindings return bindings
} }
/**
* Gets all row bindings that are globally available.
*/
const getRowBindings = () => {
let bindings = []
if (get(store).clientFeatures?.rowSelection) {
const safeState = makePropSafe("rowSelection")
bindings = [
{
type: "context",
runtimeBinding: `${safeState}.${makePropSafe("row")}`,
readableBinding: "Rows",
},
]
return bindings
}
return bindings
}
/** /**
* Gets all state bindings that are globally available. * Gets all state bindings that are globally available.
*/ */
const getStateBindings = () => { const getStateBindings = () => {
let bindings = [] let bindings = []
if (get(store).clientFeatures?.state) { if (get(store).clientFeatures?.rowSelection) {
const safeState = makePropSafe("state") const safeState = makePropSafe("rowSelection")
bindings = getAllStateVariables().map(key => ({ bindings = getAllStateVariables().map(key => ({
type: "context", type: "context",
runtimeBinding: `${safeState}.${makePropSafe(key)}`, runtimeBinding: `${safeState}.${makePropSafe(key)}`,

View File

@ -42,6 +42,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,

View File

@ -66,7 +66,6 @@ export function createAppStore() {
} }
async function update(appId, value) { async function update(appId, value) {
console.log({ value })
const response = await api.put(`/api/applications/${appId}`, { ...value }) const response = await api.put(`/api/applications/${appId}`, { ...value })
if (response.status === 200) { if (response.status === 200) {
store.update(state => { store.update(state => {

View File

@ -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",
@ -2707,6 +2708,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",
@ -2961,6 +2969,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",

View File

@ -19,6 +19,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} {#key $screenStore.activeLayout._id}
<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>

View File

@ -14,9 +14,11 @@
export let linkURL export let linkURL
export let linkColumn export let linkColumn
export let linkPeek export let linkPeek
export let allowSelectRows
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 = [
{ {
@ -24,7 +26,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 +38,9 @@
ActionTypes.SetDataProviderSorting ActionTypes.SetDataProviderSorting
) )
$: {
rowSelectionStore.actions.update(selectedRows)
}
const getFields = (schema, customColumns, showAutoColumns) => { const getFields = (schema, customColumns, showAutoColumns) => {
// Check for an invalid column selection // Check for an invalid column selection
let invalid = false let invalid = false
@ -112,7 +117,9 @@
{rowCount} {rowCount}
{quiet} {quiet}
{customRenderers} {customRenderers}
allowSelectRows={false} {allowSelectRows}
bind:selectedRows
allowSelectAllRows={true}
allowEditRows={false} allowEditRows={false}
allowEditColumns={false} allowEditColumns={false}
showAutoColumns={true} showAutoColumns={true}

View File

@ -0,0 +1,8 @@
<script>
import Provider from "./Provider.svelte"
import { rowSelectionStore } from "stores"
</script>
<Provider key="rowSelection" data={$rowSelectionStore}>
<slot />
</Provider>

View File

@ -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,

View File

@ -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"

View File

@ -0,0 +1,21 @@
import { writable } from "svelte/store"
const createRowSelectionStore = () => {
const store = writable([])
function update(rows) {
console.log(rows)
store.update(state => {
state = rows
return state
})
}
return {
subscribe: store.subscribe,
actions: {
update,
},
}
}
export const rowSelectionStore = createRowSelectionStore()