budibase/packages/standard-components/src/DataProvider.svelte

269 lines
6.5 KiB
Svelte
Raw Normal View History

<script>
import { getContext } from "svelte"
export let dataSource
export let filter
export let sortColumn
export let sortOrder
export let limit = 50
const { API, styleable, Provider, ActionTypes } = getContext("sdk")
const component = getContext("component")
// Loading flag every time data is being fetched
let loading = false
// Loading flag for the initial load
let loaded = false
// Provider state
let rows = []
2021-05-11 12:24:16 +02:00
let allRows = []
2021-03-18 16:53:25 +01:00
let schema = {}
let bookmarks = [null]
let pageNumber = 0
2021-03-18 16:53:25 +01:00
2021-05-11 12:24:16 +02:00
$: internalTable = dataSource?.type === "table"
$: query = internalTable ? buildLuceneQuery(filter) : null
$: hasNextPage = bookmarks[pageNumber + 1] != null
$: hasPrevPage = pageNumber > 0
2021-03-18 16:53:25 +01:00
$: getSchema(dataSource)
2021-05-11 12:24:16 +02:00
$: sortType = getSortType(schema, sortColumn)
$: fetchData(dataSource, query, limit, sortColumn, sortOrder, sortType)
$: {
if (internalTable) {
rows = allRows
} else {
const sortedRows = sortRows(allRows, sortColumn, sortOrder)
rows = limitRows(sortedRows, limit)
2021-05-11 12:24:16 +02:00
}
}
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => fetchData(dataSource),
metadata: { dataSource },
},
{
type: ActionTypes.NextPage,
callback: () => nextPage(),
},
{
type: ActionTypes.PrevPage,
callback: () => prevPage(),
},
]
$: dataContext = {
rows,
2021-03-18 16:53:25 +01:00
schema,
rowsLength: rows.length,
loading,
loaded,
pageNumber: pageNumber + 1,
hasNextPage,
hasPrevPage,
}
2021-05-11 12:24:16 +02:00
const getSortType = (schema, sortColumn) => {
if (!schema || !sortColumn || !schema[sortColumn]) {
return "string"
}
const type = schema?.[sortColumn]?.type
return type === "number" ? "number" : "string"
}
const buildLuceneQuery = filter => {
let query = {
string: {},
fuzzy: {},
range: {},
equal: {},
notEqual: {},
empty: {},
notEmpty: {},
}
if (Array.isArray(filter)) {
filter.forEach(({ operator, field, type, value }) => {
if (operator.startsWith("range")) {
if (!query.range[field]) {
query.range[field] = {
low: type === "number" ? Number.MIN_SAFE_INTEGER : "0000",
high: type === "number" ? Number.MAX_SAFE_INTEGER : "9999",
}
}
if (operator === "rangeLow") {
query.range[field].low = value
} else if (operator === "rangeHigh") {
query.range[field].high = value
}
} else if (query[operator]) {
query[operator][field] = value
}
})
}
return query
}
2021-05-11 12:24:16 +02:00
const fetchData = async (
dataSource,
query,
limit,
sortColumn,
sortOrder,
sortType
) => {
loading = true
if (dataSource?.type === "table") {
const res = await API.searchTable({
tableId: dataSource.tableId,
query,
limit,
sort: sortColumn,
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
2021-05-11 12:24:16 +02:00
sortType,
})
pageNumber = 0
2021-05-11 12:24:16 +02:00
allRows = res.rows
// Check we have next data
const next = await API.searchTable({
tableId: dataSource.tableId,
query,
limit: 1,
bookmark: res.bookmark,
sort: sortColumn,
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
2021-05-11 12:24:16 +02:00
sortType,
})
if (next.rows?.length) {
bookmarks = [null, res.bookmark]
} else {
bookmarks = [null]
}
} else {
const rows = await API.fetchDatasource(dataSource)
2021-05-11 12:24:16 +02:00
allRows = inMemoryFilterRows(rows, filter)
}
loading = false
loaded = true
}
const inMemoryFilterRows = (rows, filter) => {
let filteredData = [...rows]
2021-05-11 12:24:16 +02:00
Object.entries(filter || {}).forEach(([field, value]) => {
if (value != null && value !== "") {
2021-05-11 12:24:16 +02:00
filteredData = filteredData.filter(row => {
return row[field] === value
})
}
})
return filteredData
}
const sortRows = (rows, sortColumn, sortOrder) => {
if (!sortColumn || !sortOrder) {
return rows
}
return rows.slice().sort((a, b) => {
const colA = a[sortColumn]
const colB = b[sortColumn]
if (sortOrder === "Descending") {
return colA > colB ? -1 : 1
} else {
return colA > colB ? 1 : -1
}
})
}
const limitRows = (rows, limit) => {
const numLimit = parseFloat(limit)
if (isNaN(numLimit)) {
return rows
}
return rows.slice(0, numLimit)
}
2021-03-18 16:53:25 +01:00
2021-05-11 12:24:16 +02:00
const getSchema = async dataSource => {
2021-03-18 16:53:25 +01:00
if (dataSource?.schema) {
schema = dataSource.schema
} else if (dataSource?.tableId) {
const definition = await API.fetchTableDefinition(dataSource.tableId)
schema = definition?.schema ?? {}
} else {
schema = {}
}
// Ensure there are "name" properties for all fields and that field schema
// are objects
let fixedSchema = {}
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
if (typeof fieldSchema === "string") {
fixedSchema[fieldName] = {
type: fieldSchema,
name: fieldName,
}
} else {
fixedSchema[fieldName] = {
...fieldSchema,
name: fieldName,
}
}
})
schema = fixedSchema
2021-03-18 16:53:25 +01:00
}
const nextPage = async () => {
if (!hasNextPage || !internalTable) {
return
}
const res = await API.searchTable({
tableId: dataSource?.tableId,
query,
bookmark: bookmarks[pageNumber + 1],
limit,
sort: sortColumn,
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
2021-05-11 12:24:16 +02:00
sortType,
})
pageNumber++
2021-05-11 12:24:16 +02:00
allRows = res.rows
// Check we have next data
const next = await API.searchTable({
tableId: dataSource.tableId,
query,
limit: 1,
bookmark: res.bookmark,
sort: sortColumn,
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
2021-05-11 12:24:16 +02:00
sortType,
})
if (next.rows?.length) {
bookmarks[pageNumber + 1] = res.bookmark
}
}
const prevPage = async () => {
if (!hasPrevPage || !internalTable) {
return
}
const res = await API.searchTable({
tableId: dataSource?.tableId,
query,
bookmark: bookmarks[pageNumber - 1],
limit,
sort: sortColumn,
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
2021-05-11 12:24:16 +02:00
sortType,
})
pageNumber--
2021-05-11 12:24:16 +02:00
allRows = res.rows
}
</script>
<div use:styleable={$component.styles}>
<Provider {actions} data={dataContext}>
<slot />
</Provider>
</div>