Browse Source

add row selection functionality to tables

pull/4638/head
Peter Clement 5 years ago
parent
commit
350edc2aec
  1. 33
      packages/bbui/src/Table/Table.svelte
  2. 26
      packages/builder/src/builderStore/dataBinding.js
  3. 1
      packages/builder/src/builderStore/store/frontend.js
  4. 1
      packages/builder/src/stores/portal/apps.js
  5. 15
      packages/client/manifest.json
  6. 85
      packages/client/src/components/ClientApp.svelte
  7. 13
      packages/client/src/components/app/table/Table.svelte
  8. 8
      packages/client/src/components/context/RowSelectionProvider.svelte
  9. 2
      packages/client/src/sdk.js
  10. 2
      packages/client/src/stores/index.js
  11. 21
      packages/client/src/stores/rowSelection.js

33
packages/bbui/src/Table/Table.svelte

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

26
packages/builder/src/builderStore/dataBinding.js

@ -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)}`,

1
packages/builder/src/builderStore/store/frontend.js

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

1
packages/builder/src/stores/portal/apps.js

@ -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 => {

15
packages/client/manifest.json

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

85
packages/client/src/components/ClientApp.svelte

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

13
packages/client/src/components/app/table/Table.svelte

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

8
packages/client/src/components/context/RowSelectionProvider.svelte

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

2
packages/client/src/sdk.js

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

2
packages/client/src/stores/index.js

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

21
packages/client/src/stores/rowSelection.js

@ -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…
Cancel
Save