Add settings to grid block for filtering and sorting, and refactor grid logic to allow external control of these stores
This commit is contained in:
parent
6438d23bed
commit
68f21274ed
|
@ -4,6 +4,9 @@
|
|||
|
||||
const { columns, tableId, filter, table } = getContext("grid")
|
||||
|
||||
// Wipe filter whenever table ID changes to avoid using stale filters
|
||||
$: $tableId, filter.set([])
|
||||
|
||||
const onFilter = e => {
|
||||
filter.set(e.detail || [])
|
||||
}
|
||||
|
|
|
@ -5237,6 +5237,7 @@
|
|||
"width": 600,
|
||||
"height": 400
|
||||
},
|
||||
"info": "Grid Block is only compatible with internal or SQL tables",
|
||||
"settings": [
|
||||
{
|
||||
"type": "table",
|
||||
|
@ -5244,6 +5245,23 @@
|
|||
"key": "table",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "filter",
|
||||
"label": "Filtering",
|
||||
"key": "filter"
|
||||
},
|
||||
{
|
||||
"type": "field/sortable",
|
||||
"label": "Sort column",
|
||||
"key": "sortColumn"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Sort order",
|
||||
"key": "sortOrder",
|
||||
"options": ["Ascending", "Descending"],
|
||||
"defaultValue": "Ascending"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Add rows",
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
export let allowEditRows = true
|
||||
export let allowDeleteRows = true
|
||||
export let stripeRows = false
|
||||
export let filter = null
|
||||
export let sortColumn = null
|
||||
export let sortOrder = null
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable, API, builderStore } = getContext("sdk")
|
||||
|
@ -25,6 +28,9 @@
|
|||
{allowEditRows}
|
||||
{allowDeleteRows}
|
||||
{stripeRows}
|
||||
{filter}
|
||||
{sortColumn}
|
||||
{sortOrder}
|
||||
showControls={false}
|
||||
allowExpandRows={false}
|
||||
allowSchemaChanges={false}
|
||||
|
|
|
@ -42,6 +42,8 @@
|
|||
let candidateIndex
|
||||
let lastSearchId
|
||||
let searching = false
|
||||
let valuesHeight = 0
|
||||
let container
|
||||
|
||||
$: oneRowOnly = schema?.relationshipType === "one-to-many"
|
||||
$: editable = focused && !readonly
|
||||
|
@ -138,6 +140,8 @@
|
|||
|
||||
const open = async () => {
|
||||
isOpen = true
|
||||
valuesHeight = container.getBoundingClientRect().height
|
||||
console.log(valuesHeight)
|
||||
|
||||
// Find the primary display for the related table
|
||||
if (!primaryDisplay) {
|
||||
|
@ -243,7 +247,7 @@
|
|||
</script>
|
||||
|
||||
<div class="wrapper" class:editable class:focused style="--color:{color};">
|
||||
<div class="container">
|
||||
<div class="container" bind:this={container}>
|
||||
<div
|
||||
class="values"
|
||||
class:wrap={editable || contentLines > 1}
|
||||
|
@ -290,6 +294,7 @@
|
|||
class:invertY
|
||||
on:wheel|stopPropagation
|
||||
use:clickOutside={close}
|
||||
style="--values-height:{valuesHeight}px;"
|
||||
>
|
||||
<div class="search">
|
||||
<Input
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { setContext, onMount } from "svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import { fade } from "svelte/transition"
|
||||
import { clickOutside, ProgressCircle } from "@budibase/bbui"
|
||||
import { createEventManagers } from "../lib/events"
|
||||
|
@ -30,6 +29,7 @@
|
|||
GutterWidth,
|
||||
DefaultRowHeight,
|
||||
} from "../lib/constants"
|
||||
import { memo } from "../../../utils"
|
||||
|
||||
export let API = null
|
||||
export let tableId = null
|
||||
|
@ -43,19 +43,29 @@
|
|||
export let collaboration = true
|
||||
export let showAvatars = true
|
||||
export let showControls = true
|
||||
export let filter = null
|
||||
export let sortColumn = null
|
||||
export let sortOrder = null
|
||||
|
||||
// Unique identifier for DOM nodes inside this instance
|
||||
const rand = Math.random()
|
||||
|
||||
// State stores
|
||||
const tableIdStore = writable(tableId)
|
||||
const schemaOverridesStore = writable(schemaOverrides)
|
||||
const config = writable({
|
||||
// Stores derived from props.
|
||||
// We use memo here to ensure redundant store reactions don't fire and cause
|
||||
// wasted API calls.
|
||||
const tableIdStore = memo(tableId)
|
||||
const schemaOverridesStore = memo(schemaOverrides)
|
||||
const filterStore = memo(filter)
|
||||
const sortStore = memo({
|
||||
column: sortColumn,
|
||||
order: sortOrder,
|
||||
})
|
||||
const config = memo({
|
||||
allowAddRows,
|
||||
allowSchemaChanges,
|
||||
allowExpandRows,
|
||||
allowEditRows,
|
||||
allowDeleteRows,
|
||||
allowSchemaChanges,
|
||||
stripeRows,
|
||||
showControls,
|
||||
})
|
||||
|
@ -67,6 +77,8 @@
|
|||
config,
|
||||
tableId: tableIdStore,
|
||||
schemaOverrides: schemaOverridesStore,
|
||||
filter: filterStore,
|
||||
sort: sortStore,
|
||||
}
|
||||
context = { ...context, ...createEventManagers() }
|
||||
context = attachStores(context)
|
||||
|
@ -83,9 +95,14 @@
|
|||
gridFocused,
|
||||
} = context
|
||||
|
||||
// Keep stores up to date
|
||||
// Keep prop-derived stores up to date
|
||||
$: tableIdStore.set(tableId)
|
||||
$: schemaOverridesStore.set(schemaOverrides)
|
||||
$: filterStore.set(filter)
|
||||
$: sortStore.set({
|
||||
column: sortColumn,
|
||||
order: sortOrder,
|
||||
})
|
||||
$: config.set({
|
||||
allowAddRows,
|
||||
allowSchemaChanges,
|
||||
|
@ -115,10 +132,10 @@
|
|||
id="grid-{rand}"
|
||||
class:is-resizing={$isResizing}
|
||||
class:is-reordering={$isReordering}
|
||||
class:stripe={$config.stripeRows}
|
||||
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
|
||||
class:stripe={stripeRows}
|
||||
on:mouseenter={() => gridFocused.set(true)}
|
||||
on:mouseleave={() => gridFocused.set(false)}
|
||||
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
|
||||
>
|
||||
{#if showControls}
|
||||
<div class="controls">
|
||||
|
@ -170,6 +187,7 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
/* Core grid */
|
||||
.grid {
|
||||
/* Variables */
|
||||
--grid-background: var(--spectrum-global-color-gray-50);
|
||||
|
@ -182,7 +200,6 @@
|
|||
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
||||
--cell-font-size: 14px;
|
||||
--controls-height: 50px;
|
||||
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -206,6 +223,7 @@
|
|||
--cell-background-alt: var(--spectrum-global-color-gray-75);
|
||||
}
|
||||
|
||||
/* Data layers */
|
||||
.grid-data-outer,
|
||||
.grid-data-inner {
|
||||
flex: 1 1 auto;
|
||||
|
|
|
@ -2,19 +2,13 @@ import { writable, derived, get } from "svelte/store"
|
|||
import { fetchData } from "../../../fetch/fetchData"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { NewRowID, RowPageSize } from "../lib/constants"
|
||||
|
||||
const initialSortState = {
|
||||
column: null,
|
||||
order: "ascending",
|
||||
}
|
||||
import { tick } from "svelte"
|
||||
|
||||
export const createStores = () => {
|
||||
const rows = writable([])
|
||||
const table = writable(null)
|
||||
const filter = writable([])
|
||||
const loading = writable(false)
|
||||
const loaded = writable(false)
|
||||
const sort = writable(initialSortState)
|
||||
const rowChangeCache = writable({})
|
||||
const inProgressChanges = writable({})
|
||||
const hasNextPage = writable(false)
|
||||
|
@ -46,10 +40,8 @@ export const createStores = () => {
|
|||
rows,
|
||||
rowLookupMap,
|
||||
table,
|
||||
filter,
|
||||
loaded,
|
||||
loading,
|
||||
sort,
|
||||
rowChangeCache,
|
||||
inProgressChanges,
|
||||
hasNextPage,
|
||||
|
@ -97,15 +89,18 @@ export const deriveStores = context => {
|
|||
// Reset everything when table ID changes
|
||||
let unsubscribe = null
|
||||
let lastResetKey = null
|
||||
tableId.subscribe($tableId => {
|
||||
tableId.subscribe(async $tableId => {
|
||||
// Unsub from previous fetch if one exists
|
||||
unsubscribe?.()
|
||||
fetch.set(null)
|
||||
instanceLoaded.set(false)
|
||||
loading.set(true)
|
||||
|
||||
// Reset state
|
||||
filter.set([])
|
||||
// Tick to allow other reactive logic to update stores when table ID changes
|
||||
// before proceeding. This allows us to wipe filters etc if needed.
|
||||
await tick()
|
||||
const $filter = get(filter)
|
||||
const $sort = get(sort)
|
||||
|
||||
// Create new fetch model
|
||||
const newFetch = fetchData({
|
||||
|
@ -115,9 +110,9 @@ export const deriveStores = context => {
|
|||
tableId: $tableId,
|
||||
},
|
||||
options: {
|
||||
filter: [],
|
||||
sortColumn: initialSortState.column,
|
||||
sortOrder: initialSortState.order,
|
||||
filter: $filter,
|
||||
sortColumn: $sort.column,
|
||||
sortOrder: $sort.order,
|
||||
limit: RowPageSize,
|
||||
paginate: true,
|
||||
},
|
||||
|
|
|
@ -136,8 +136,10 @@ export default class DataFetch {
|
|||
this.options.sortOrder = "ascending"
|
||||
}
|
||||
|
||||
// If no sort column, use the primary display and fallback to first column
|
||||
if (!this.options.sortColumn) {
|
||||
// If no sort column, or an invalid sort column is provided, use the primary
|
||||
// display and fallback to first column
|
||||
const sortValid = this.options.sortColumn && schema[this.options.sortColumn]
|
||||
if (!sortValid) {
|
||||
let newSortColumn
|
||||
if (definition?.primaryDisplay && schema[definition.primaryDisplay]) {
|
||||
newSortColumn = definition.primaryDisplay
|
||||
|
|
|
@ -3,4 +3,5 @@ export * as JSONUtils from "./json"
|
|||
export * as CookieUtils from "./cookies"
|
||||
export * as RoleUtils from "./roles"
|
||||
export * as Utils from "./utils"
|
||||
export { memo } from "./memo"
|
||||
export { createWebsocket } from "./websocket"
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
|
||||
// A simple svelte store which deeply compares all changes and ensures that
|
||||
// subscribed children will only fire when a new value is actually set
|
||||
export const memo = initialValue => {
|
||||
const store = writable(initialValue)
|
||||
|
||||
const tryUpdateValue = (newValue, currentValue) => {
|
||||
// Sanity check for primitive equality
|
||||
if (currentValue === newValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise deep compare via JSON stringify
|
||||
const currentString = JSON.stringify(currentValue)
|
||||
const newString = JSON.stringify(newValue)
|
||||
if (currentString !== newString) {
|
||||
store.set(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
set: newValue => {
|
||||
const currentValue = get(store)
|
||||
tryUpdateValue(newValue, currentValue)
|
||||
},
|
||||
update: updateFn => {
|
||||
const currentValue = get(store)
|
||||
let mutableCurrentValue = JSON.parse(JSON.stringify(currentValue))
|
||||
const newValue = updateFn(mutableCurrentValue)
|
||||
tryUpdateValue(newValue, currentValue)
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue