add row selection functionality to tables

This commit is contained in:
Peter Clement 2022-02-11 11:55:35 +00:00
parent 14b0ef2ec5
commit d45c107db7
11 changed files with 153 additions and 54 deletions

View File

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

View File

@ -35,12 +35,14 @@ export const getBindableProperties = (asset, componentId) => {
const urlBindings = getUrlBindings(asset)
const deviceBindings = getDeviceBindings()
const stateBindings = getStateBindings()
const rowBindings = getRowBindings()
return [
...contextBindings,
...urlBindings,
...stateBindings,
...userBindings,
...deviceBindings,
...rowBindings,
]
}
@ -321,13 +323,33 @@ const getDeviceBindings = () => {
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.
*/
const getStateBindings = () => {
let bindings = []
if (get(store).clientFeatures?.state) {
const safeState = makePropSafe("state")
if (get(store).clientFeatures?.rowSelection) {
const safeState = makePropSafe("rowSelection")
bindings = getAllStateVariables().map(key => ({
type: "context",
runtimeBinding: `${safeState}.${makePropSafe(key)}`,

View File

@ -42,6 +42,7 @@ const INITIAL_FRONTEND_STATE = {
intelligentLoading: false,
deviceAwareness: false,
state: false,
rowSelection: false,
customThemes: false,
devicePreview: false,
messagePassing: false,

View File

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

View File

@ -6,7 +6,8 @@
"state": true,
"customThemes": true,
"devicePreview": true,
"messagePassing": true
"messagePassing": true,
"rowSelection": true
},
"layout": {
"name": "Layout",
@ -2707,6 +2708,13 @@
"key": "showAutoColumns",
"defaultValue": false
},
{
"type": "boolean",
"label": "Allow row selection",
"key": "allowSelectRows",
"defaultValue": false
},
{
"type": "boolean",
"label": "Link table rows",
@ -2961,6 +2969,11 @@
"label": "Show auto columns",
"key": "showAutoColumns"
},
{
"type": "boolean",
"label": "Allow row selection",
"key": "allowSelectRows"
},
{
"type": "boolean",
"label": "Link table rows",

View File

@ -19,6 +19,7 @@
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
import RowSelectionProvider from "components/context/RowSelectionProvider.svelte"
import SettingsBar from "components/preview/SettingsBar.svelte"
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
import HoverIndicator from "components/preview/HoverIndicator.svelte"
@ -90,59 +91,61 @@
<UserBindingsProvider>
<DeviceBindingsProvider>
<StateBindingsProvider>
<!-- Settings bar can be rendered outside of device preview -->
<!-- Key block needs to be outside the if statement or it breaks -->
{#key $builderStore.selectedComponentId}
{#if $builderStore.inBuilder}
<SettingsBar />
{/if}
{/key}
<RowSelectionProvider>
<!-- Settings bar can be rendered outside of device preview -->
<!-- Key block needs to be outside the if statement or it breaks -->
{#key $builderStore.selectedComponentId}
{#if $builderStore.inBuilder}
<SettingsBar />
{/if}
{/key}
<!-- Clip boundary for selection indicators -->
<div
id="clip-root"
class:preview={$builderStore.inBuilder}
class:tablet-preview={$builderStore.previewDevice === "tablet"}
class:mobile-preview={$builderStore.previewDevice === "mobile"}
>
<!-- Actual app -->
<div id="app-root">
<CustomThemeWrapper>
{#key $screenStore.activeLayout._id}
<Component
isLayout
instance={$screenStore.activeLayout.props}
/>
{/key}
<!-- Clip boundary for selection indicators -->
<div
id="clip-root"
class:preview={$builderStore.inBuilder}
class:tablet-preview={$builderStore.previewDevice === "tablet"}
class:mobile-preview={$builderStore.previewDevice === "mobile"}
>
<!-- Actual app -->
<div id="app-root">
<CustomThemeWrapper>
{#key $screenStore.activeLayout._id}
<Component
isLayout
instance={$screenStore.activeLayout.props}
/>
{/key}
<!--
<!--
Flatpickr needs to be inside the theme wrapper.
It also needs its own container because otherwise it hijacks
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 -->
<div class="modal-container" />
<!-- Modal container to ensure they sit on top -->
<div class="modal-container" />
<!-- Layers on top of app -->
<NotificationDisplay />
<ConfirmationDisplay />
<PeekScreenDisplay />
</CustomThemeWrapper>
</div>
<!-- Layers on top of app -->
<NotificationDisplay />
<ConfirmationDisplay />
<PeekScreenDisplay />
</CustomThemeWrapper>
</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
re-mounting to avoid flashes.
-->
{#if $builderStore.inBuilder}
<SelectionIndicator />
<HoverIndicator />
<DNDHandler />
{/if}
</div>
{#if $builderStore.inBuilder}
<SelectionIndicator />
<HoverIndicator />
<DNDHandler />
{/if}
</div>
</RowSelectionProvider>
</StateBindingsProvider>
</DeviceBindingsProvider>
</UserBindingsProvider>

View File

@ -14,9 +14,11 @@
export let linkURL
export let linkColumn
export let linkPeek
export let allowSelectRows
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 customRenderers = [
{
@ -24,7 +26,7 @@
component: SlotRenderer,
},
]
let selectedRows = []
$: hasChildren = $component.children
$: loading = dataProvider?.loading ?? false
$: data = dataProvider?.rows || []
@ -36,6 +38,9 @@
ActionTypes.SetDataProviderSorting
)
$: {
rowSelectionStore.actions.update(selectedRows)
}
const getFields = (schema, customColumns, showAutoColumns) => {
// Check for an invalid column selection
let invalid = false
@ -112,7 +117,9 @@
{rowCount}
{quiet}
{customRenderers}
allowSelectRows={false}
{allowSelectRows}
bind:selectedRows
allowSelectAllRows={true}
allowEditRows={false}
allowEditColumns={false}
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,
builderStore,
uploadStore,
rowSelectionStore,
} from "stores"
import { styleable } from "utils/styleable"
import { linkable } from "utils/linkable"
@ -19,6 +20,7 @@ export default {
authStore,
notificationStore,
routeStore,
rowSelectionStore,
screenStore,
builderStore,
uploadStore,

View File

@ -10,7 +10,7 @@ export { peekStore } from "./peek"
export { stateStore } from "./state"
export { themeStore } from "./theme"
export { uploadStore } from "./uploads.js"
export { rowSelectionStore } from "./rowSelection.js"
// Context stores are layered and duplicated, so it is not a singleton
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()