add row selection functionality to tables
This commit is contained in:
parent
0829723d99
commit
350edc2aec
|
@ -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 {
|
||||||
|
|
|
@ -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)}`,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue