Add multiple validation improvements
This commit is contained in:
parent
6c203a1e66
commit
4a6713e9d3
|
@ -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(() => {
|
||||||
|
|
|
@ -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]}`
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue