Add multiple validation improvements

This commit is contained in:
Andrew Kingston 2023-04-11 16:34:13 +01:00
parent 6c203a1e66
commit 4a6713e9d3
6 changed files with 117 additions and 60 deletions

View File

@ -24,6 +24,12 @@
let lastSearchId let lastSearchId
let results let results
$: {
if (focused) {
console.log(value)
}
}
$: oneRowOnly = schema?.relationshipType === "one-to-many" $: oneRowOnly = schema?.relationshipType === "one-to-many"
$: editable = focused && !readonly $: editable = focused && !readonly
$: results = getResults(searchResults, value) $: results = getResults(searchResults, value)
@ -52,6 +58,7 @@
if (!row?._id) { if (!row?._id) {
return false return false
} }
console.log(lookupMap)
return lookupMap?.[row._id] === true return lookupMap?.[row._id] === true
} }
@ -192,11 +199,7 @@
} }
candidateIndex = null candidateIndex = null
} }
close()
// Clear search state to allow finding a new row again
searchString = null
searchResults = []
lastSearchString = null
} }
onMount(() => { onMount(() => {

View File

@ -3,7 +3,7 @@
import { debounce } from "../../../utils/utils" import { debounce } from "../../../utils/utils"
const { const {
rows, enrichedRows,
focusedCellId, focusedCellId,
visibleColumns, visibleColumns,
focusedRow, focusedRow,
@ -63,7 +63,7 @@
} }
const selectFirstCell = () => { const selectFirstCell = () => {
const firstRow = $rows[0] const firstRow = $enrichedRows[0]
if (!firstRow) { if (!firstRow) {
return return
} }
@ -102,7 +102,7 @@
if (!$focusedRow) { if (!$focusedRow) {
return return
} }
const newRow = $rows[$focusedRow.__idx + delta] const newRow = $enrichedRows[$focusedRow.__idx + delta]
if (newRow) { if (newRow) {
const split = $focusedCellId.split("-") const split = $focusedCellId.split("-")
$focusedCellId = `${newRow._id}-${split[1]}` $focusedCellId = `${newRow._id}-${split[1]}`

View File

@ -13,18 +13,8 @@ export const createStores = () => {
const filter = writable([]) const filter = writable([])
const loaded = writable(false) const loaded = writable(false)
const sort = writable(initialSortState) const sort = writable(initialSortState)
const rowChangeCache = writable({})
// Enrich rows with an index property const inProgressChanges = writable({})
const enrichedRows = derived(
rows,
$rows => {
return $rows.map((row, idx) => ({
...row,
__idx: idx,
}))
},
[]
)
// Generate a lookup map to quick find a row by ID // Generate a lookup map to quick find a row by ID
const rowLookupMap = derived( const rowLookupMap = derived(
@ -40,15 +30,14 @@ export const createStores = () => {
) )
return { return {
rows: { rows,
...rows,
subscribe: enrichedRows.subscribe,
},
rowLookupMap, rowLookupMap,
table, table,
filter, filter,
loaded, loaded,
sort, sort,
rowChangeCache,
inProgressChanges,
} }
} }
@ -66,6 +55,9 @@ export const deriveStores = context => {
validation, validation,
focusedCellId, focusedCellId,
columns, columns,
rowChangeCache,
inProgressChanges,
previousFocusedRowId,
} = context } = context
const instanceLoaded = writable(false) const instanceLoaded = writable(false)
const fetch = writable(null) const fetch = writable(null)
@ -73,6 +65,19 @@ export const deriveStores = context => {
// Local cache of row IDs to speed up checking if a row exists // Local cache of row IDs to speed up checking if a row exists
let rowCacheMap = {} let rowCacheMap = {}
// Enrich rows with an index property and any pending changes
const enrichedRows = derived(
[rows, rowChangeCache],
([$rows, $rowChangeCache]) => {
return $rows.map((row, idx) => ({
...row,
...$rowChangeCache[row._id],
__idx: idx,
}))
},
[]
)
// Reset everything when table ID changes // Reset everything when table ID changes
let unsubscribe = null let unsubscribe = null
tableId.subscribe($tableId => { tableId.subscribe($tableId => {
@ -153,6 +158,7 @@ export const deriveStores = context => {
// state, storing error messages against relevant cells // state, storing error messages against relevant cells
const handleValidationError = (rowId, error) => { const handleValidationError = (rowId, error) => {
if (error?.json?.validationErrors) { if (error?.json?.validationErrors) {
// Normal validation error
const keys = Object.keys(error.json.validationErrors) const keys = Object.keys(error.json.validationErrors)
const $columns = get(columns) const $columns = get(columns)
for (let column of keys) { for (let column of keys) {
@ -173,7 +179,8 @@ export const deriveStores = context => {
// Focus the first cell with an error // Focus the first cell with an error
focusedCellId.set(`${rowId}-${keys[0]}`) focusedCellId.set(`${rowId}-${keys[0]}`)
} else { } else {
notifications.error(`Error saving row: ${error?.message}`) // Some other error - just update the current cell
validation.actions.setError(get(focusedCellId), error?.message || "Error")
} }
} }
@ -254,25 +261,42 @@ export const deriveStores = context => {
} }
// Immediately update state so that the change is reflected // Immediately update state so that the change is reflected
let newRow = { ...row, [column]: value } rowChangeCache.update(state => ({
rows.update(state => { ...state,
state[index] = { ...newRow } [rowId]: {
return state ...state[rowId],
}) [column]: value,
},
}))
// Save change // Save change
delete newRow.__idx
try { try {
await API.saveRow(newRow) inProgressChanges.update(state => ({
} catch (error) { ...state,
handleValidationError(newRow._id, error) [rowId]: true,
}))
const newRow = { ...row, ...get(rowChangeCache)[rowId] }
const saved = await API.saveRow(newRow)
// Revert change // Update state after a successful change
rows.update(state => { rows.update(state => {
state[index] = row state[index] = {
return state ...newRow,
}) _rev: saved._rev,
} }
return state.slice()
})
rowChangeCache.update(state => ({
...state,
[rowId]: null,
}))
} catch (error) {
handleValidationError(rowId, error)
}
inProgressChanges.update(state => ({
...state,
[rowId]: false,
}))
} }
// Deletes an array of rows // Deletes an array of rows
@ -338,7 +362,18 @@ export const deriveStores = context => {
return get(rowLookupMap)[id] != null return get(rowLookupMap)[id] != null
} }
// Wipe the row change cache when changing row
previousFocusedRowId.subscribe(id => {
if (!get(inProgressChanges)[id]) {
rowChangeCache.update(state => ({
...state,
[id]: null,
}))
}
})
return { return {
enrichedRows,
rows: { rows: {
...rows, ...rows,
actions: { actions: {

View File

@ -6,9 +6,29 @@ export const createStores = () => {
const selectedRows = writable({}) const selectedRows = writable({})
const hoveredRowId = writable(null) const hoveredRowId = writable(null)
const rowHeight = writable(36) const rowHeight = writable(36)
const previousFocusedRowId = writable(null)
// Derive the current focused row ID
const focusedRowId = derived(
focusedCellId,
$focusedCellId => {
return $focusedCellId?.split("-")[0]
},
null
)
// Remember the last focused row ID so that we can store the previous one
let lastFocusedRowId = null
focusedRowId.subscribe(id => {
previousFocusedRowId.set(lastFocusedRowId)
lastFocusedRowId = id
})
return { return {
focusedCellId, focusedCellId,
focusedCellAPI, focusedCellAPI,
focusedRowId,
previousFocusedRowId,
selectedRows, selectedRows,
hoveredRowId, hoveredRowId,
rowHeight, rowHeight,
@ -16,13 +36,19 @@ export const createStores = () => {
} }
export const deriveStores = context => { export const deriveStores = context => {
const { focusedCellId, selectedRows, hoveredRowId, rows, rowLookupMap } = const {
context rows,
focusedCellId,
selectedRows,
hoveredRowId,
enrichedRows,
rowLookupMap,
} = context
// Derive the row that contains the selected cell // Derive the row that contains the selected cell
const focusedRow = derived( const focusedRow = derived(
[focusedCellId, rowLookupMap, rows], [focusedCellId, rowLookupMap, enrichedRows],
([$focusedCellId, $rowLookupMap, $rows]) => { ([$focusedCellId, $rowLookupMap, $enrichedRows]) => {
const rowId = $focusedCellId?.split("-")[0] const rowId = $focusedCellId?.split("-")[0]
if (rowId === "new") { if (rowId === "new") {
@ -31,7 +57,7 @@ export const deriveStores = context => {
} else { } else {
// All normal rows // All normal rows
const index = $rowLookupMap[rowId] const index = $rowLookupMap[rowId]
return $rows[index] return $enrichedRows[index]
} }
}, },
null null

View File

@ -1,4 +1,4 @@
import { writable, get, derived } from "svelte/store" import { writable, get } from "svelte/store"
export const createStores = () => { export const createStores = () => {
const validation = writable({}) const validation = writable({})
@ -24,29 +24,22 @@ export const createStores = () => {
} }
export const deriveStores = context => { export const deriveStores = context => {
const { validation, focusedRow, columns, stickyColumn } = context const { validation, previousFocusedRowId, columns, stickyColumn } = context
const focusedRowId = derived(focusedRow, $focusedRow => $focusedRow?._id)
// Store the row ID that was previously focused, so we can remove errors from
// it when we focus a new row
let previousFocusedRowId = null
focusedRowId.subscribe(id => {
// Remove validation errors from previous focused row // Remove validation errors from previous focused row
if (previousFocusedRowId) { previousFocusedRowId.subscribe(id => {
if (id) {
const $columns = get(columns) const $columns = get(columns)
const $stickyColumn = get(stickyColumn) const $stickyColumn = get(stickyColumn)
validation.update(state => { validation.update(state => {
$columns.forEach(column => { $columns.forEach(column => {
state[`${previousFocusedRowId}-${column.name}`] = null state[`${id}-${column.name}`] = null
}) })
if ($stickyColumn) { if ($stickyColumn) {
state[`${previousFocusedRowId}-${$stickyColumn.name}`] = null state[`${id}-${$stickyColumn.name}`] = null
} }
return state return state
}) })
} }
// Store row ID
previousFocusedRowId = id
}) })
} }

View File

@ -4,7 +4,7 @@ export const deriveStores = context => {
const { const {
rowHeight, rowHeight,
visibleColumns, visibleColumns,
rows, enrichedRows,
scrollTop, scrollTop,
scrollLeft, scrollLeft,
width, width,
@ -29,9 +29,9 @@ export const deriveStores = context => {
0 0
) )
const renderedRows = derived( const renderedRows = derived(
[rows, scrolledRowCount, visualRowCapacity], [enrichedRows, scrolledRowCount, visualRowCapacity],
([$rows, $scrolledRowCount, $visualRowCapacity]) => { ([$enrichedRows, $scrolledRowCount, $visualRowCapacity]) => {
return $rows.slice( return $enrichedRows.slice(
$scrolledRowCount, $scrolledRowCount,
$scrolledRowCount + $visualRowCapacity $scrolledRowCount + $visualRowCapacity
) )