2021-02-01 14:15:35 +01:00
|
|
|
<script>
|
2023-09-21 11:39:02 +02:00
|
|
|
import { CoreSelect, CoreMultiselect } from "@budibase/bbui"
|
|
|
|
import { fetchData } from "@budibase/frontend-core"
|
2021-02-01 14:15:35 +01:00
|
|
|
import { getContext } from "svelte"
|
2021-02-05 11:53:25 +01:00
|
|
|
import Field from "./Field.svelte"
|
2021-11-08 18:25:05 +01:00
|
|
|
import { FieldTypes } from "../../../constants"
|
2021-02-01 14:15:35 +01:00
|
|
|
|
|
|
|
const { API } = getContext("sdk")
|
|
|
|
|
|
|
|
export let field
|
|
|
|
export let label
|
2021-02-12 16:47:20 +01:00
|
|
|
export let placeholder
|
2021-02-17 16:16:44 +01:00
|
|
|
export let disabled = false
|
2021-08-10 15:37:14 +02:00
|
|
|
export let validation
|
2023-09-06 17:38:11 +02:00
|
|
|
export let autocomplete = true
|
2022-02-04 09:50:56 +01:00
|
|
|
export let defaultValue
|
2022-04-14 11:01:14 +02:00
|
|
|
export let onChange
|
2023-07-18 10:36:20 +02:00
|
|
|
export let filter
|
2023-09-20 16:44:26 +02:00
|
|
|
export let datasourceType = "table"
|
2023-09-20 17:22:07 +02:00
|
|
|
export let primaryDisplay
|
2021-02-01 14:15:35 +01:00
|
|
|
|
|
|
|
let fieldState
|
|
|
|
let fieldApi
|
|
|
|
let fieldSchema
|
|
|
|
let tableDefinition
|
2023-09-22 13:04:44 +02:00
|
|
|
let searchTerm
|
2023-09-25 12:23:17 +02:00
|
|
|
let open
|
2023-10-13 12:44:15 +02:00
|
|
|
let hasFetchedDefaultValue
|
2021-02-22 12:40:24 +01:00
|
|
|
|
2023-09-20 17:22:07 +02:00
|
|
|
$: type =
|
|
|
|
datasourceType === "table" ? FieldTypes.LINK : FieldTypes.BB_REFERENCE
|
|
|
|
|
2021-04-15 20:43:18 +02:00
|
|
|
$: multiselect = fieldSchema?.relationshipType !== "one-to-many"
|
2021-02-01 14:15:35 +01:00
|
|
|
$: linkedTableId = fieldSchema?.tableId
|
2023-07-18 10:36:20 +02:00
|
|
|
$: fetch = fetchData({
|
|
|
|
API,
|
|
|
|
datasource: {
|
2023-09-20 16:44:26 +02:00
|
|
|
type: datasourceType,
|
2023-07-18 10:36:20 +02:00
|
|
|
tableId: linkedTableId,
|
|
|
|
},
|
|
|
|
options: {
|
|
|
|
filter,
|
|
|
|
limit: 100,
|
|
|
|
},
|
|
|
|
})
|
2023-09-21 12:29:39 +02:00
|
|
|
|
2023-07-18 10:36:20 +02:00
|
|
|
$: tableDefinition = $fetch.definition
|
2023-09-22 10:14:17 +02:00
|
|
|
$: selectedValue = multiselect
|
|
|
|
? flatten(fieldState?.value) ?? []
|
|
|
|
: flatten(fieldState?.value)?.[0]
|
2021-08-17 15:13:57 +02:00
|
|
|
$: component = multiselect ? CoreMultiselect : CoreSelect
|
2022-02-04 09:50:56 +01:00
|
|
|
$: expandedDefaultValue = expand(defaultValue)
|
2023-09-20 17:22:07 +02:00
|
|
|
$: primaryDisplay = primaryDisplay || tableDefinition?.primaryDisplay
|
2023-09-22 12:16:54 +02:00
|
|
|
|
2023-09-25 10:16:01 +02:00
|
|
|
let optionsObj = {}
|
|
|
|
let initialValuesProcessed
|
2023-09-25 12:33:21 +02:00
|
|
|
|
2023-09-25 10:16:01 +02:00
|
|
|
$: {
|
|
|
|
if (!initialValuesProcessed && primaryDisplay) {
|
2023-09-25 12:33:21 +02:00
|
|
|
// Persist the initial values as options, allowing them to be present in the dropdown,
|
|
|
|
// even if they are not in the inital fetch results
|
2023-09-25 10:16:01 +02:00
|
|
|
initialValuesProcessed = true
|
2023-09-26 10:26:12 +02:00
|
|
|
optionsObj = (fieldState?.value || []).reduce((accumulator, value) => {
|
2023-09-29 15:47:07 +02:00
|
|
|
// fieldState has to be an array of strings to be valid for an update
|
|
|
|
// therefore we cannot guarantee value will be an object
|
|
|
|
// https://linear.app/budibase/issue/BUDI-7577/refactor-the-relationshipfield-component-to-have-better-support-for
|
|
|
|
if (!value._id) {
|
|
|
|
return accumulator
|
|
|
|
}
|
2023-09-25 18:21:02 +02:00
|
|
|
accumulator[value._id] = {
|
|
|
|
_id: value._id,
|
|
|
|
[primaryDisplay]: value.primaryDisplay,
|
|
|
|
}
|
|
|
|
return accumulator
|
|
|
|
}, optionsObj)
|
2023-09-25 10:16:01 +02:00
|
|
|
}
|
|
|
|
}
|
2023-09-22 12:39:49 +02:00
|
|
|
|
2023-10-13 11:22:29 +02:00
|
|
|
$: enrichedOptions = enrichOptions(optionsObj, $fetch.rows)
|
|
|
|
const enrichOptions = (optionsObj, fetchResults) => {
|
2023-09-25 18:21:02 +02:00
|
|
|
const result = (fetchResults || [])?.reduce((accumulator, row) => {
|
|
|
|
if (!accumulator[row._id]) {
|
|
|
|
accumulator[row._id] = row
|
|
|
|
}
|
|
|
|
return accumulator
|
|
|
|
}, optionsObj)
|
2023-09-25 12:40:12 +02:00
|
|
|
|
2023-10-13 11:22:29 +02:00
|
|
|
return Object.values(result)
|
2023-09-25 12:33:21 +02:00
|
|
|
}
|
2023-09-25 12:23:17 +02:00
|
|
|
$: {
|
|
|
|
// We don't want to reorder while the dropdown is open, to avoid UX jumps
|
|
|
|
if (!open) {
|
2023-09-25 12:33:21 +02:00
|
|
|
enrichedOptions = enrichedOptions.sort((a, b) => {
|
2023-09-25 12:23:17 +02:00
|
|
|
const selectedValues = flatten(fieldState?.value) || []
|
|
|
|
|
|
|
|
const aIsSelected = selectedValues.find(v => v === a._id)
|
|
|
|
const bIsSelected = selectedValues.find(v => v === b._id)
|
|
|
|
if (aIsSelected && !bIsSelected) {
|
|
|
|
return -1
|
|
|
|
} else if (!aIsSelected && bIsSelected) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return a[primaryDisplay] > b[primaryDisplay]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 20:07:04 +02:00
|
|
|
$: fetchRows(searchTerm, primaryDisplay, defaultValue)
|
2023-09-22 12:16:54 +02:00
|
|
|
|
2023-10-12 20:07:04 +02:00
|
|
|
const fetchRows = async (searchTerm, primaryDisplay, defaultVal) => {
|
2023-09-22 13:04:44 +02:00
|
|
|
const allRowsFetched =
|
|
|
|
$fetch.loaded &&
|
2023-09-25 10:16:01 +02:00
|
|
|
!Object.keys($fetch.query?.string || {}).length &&
|
2023-09-22 13:04:44 +02:00
|
|
|
!$fetch.hasNextPage
|
2023-10-12 20:00:53 +02:00
|
|
|
// Don't request until we have the primary display or default value has been fetched
|
2023-10-12 20:07:04 +02:00
|
|
|
if (allRowsFetched || !primaryDisplay) {
|
|
|
|
return
|
|
|
|
}
|
2023-10-13 12:44:15 +02:00
|
|
|
if (defaultVal && !hasFetchedDefaultValue) {
|
2023-10-12 20:00:53 +02:00
|
|
|
await fetch.update({
|
2023-10-13 12:28:23 +02:00
|
|
|
query: { equal: { _id: defaultVal } },
|
2023-09-25 10:17:26 +02:00
|
|
|
})
|
2023-10-13 12:44:15 +02:00
|
|
|
hasFetchedDefaultValue = true
|
2023-09-21 12:29:39 +02:00
|
|
|
}
|
2023-10-12 20:07:04 +02:00
|
|
|
await fetch.update({
|
|
|
|
query: { string: { [primaryDisplay]: searchTerm } },
|
|
|
|
})
|
2023-09-21 12:29:39 +02:00
|
|
|
}
|
2021-02-01 14:15:35 +01:00
|
|
|
|
2021-05-04 12:32:22 +02:00
|
|
|
const flatten = values => {
|
2021-04-16 12:32:41 +02:00
|
|
|
if (!values) {
|
|
|
|
return []
|
|
|
|
}
|
2022-08-05 15:53:41 +02:00
|
|
|
if (!Array.isArray(values)) {
|
|
|
|
values = [values]
|
|
|
|
}
|
2023-09-28 17:09:13 +02:00
|
|
|
values = values.map(value =>
|
|
|
|
typeof value === "object" ? value._id : value
|
|
|
|
)
|
|
|
|
// Make sure field state is valid
|
|
|
|
fieldApi.setValue(values)
|
|
|
|
return values
|
2021-04-16 12:32:41 +02:00
|
|
|
}
|
|
|
|
|
2021-05-04 12:32:22 +02:00
|
|
|
const getDisplayName = row => {
|
2023-09-21 12:29:39 +02:00
|
|
|
return row?.[primaryDisplay] || "-"
|
2021-02-01 14:15:35 +01:00
|
|
|
}
|
|
|
|
|
2021-05-04 12:32:22 +02:00
|
|
|
const singleHandler = e => {
|
2022-04-14 11:01:14 +02:00
|
|
|
handleChange(e.detail == null ? [] : [e.detail])
|
2021-02-01 14:15:35 +01:00
|
|
|
}
|
|
|
|
|
2021-05-04 12:32:22 +02:00
|
|
|
const multiHandler = e => {
|
2022-04-14 11:01:14 +02:00
|
|
|
handleChange(e.detail)
|
2021-02-01 14:15:35 +01:00
|
|
|
}
|
2022-02-04 09:50:56 +01:00
|
|
|
|
|
|
|
const expand = values => {
|
|
|
|
if (!values) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
if (Array.isArray(values)) {
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
return values.split(",").map(value => value.trim())
|
|
|
|
}
|
2022-04-14 11:01:14 +02:00
|
|
|
|
|
|
|
const handleChange = value => {
|
2022-08-31 12:39:04 +02:00
|
|
|
const changed = fieldApi.setValue(value)
|
|
|
|
if (onChange && changed) {
|
2023-10-09 19:18:34 +02:00
|
|
|
onChange({
|
2023-10-10 12:22:59 +02:00
|
|
|
value,
|
2023-10-09 19:18:34 +02:00
|
|
|
})
|
2022-04-14 11:01:14 +02:00
|
|
|
}
|
|
|
|
}
|
2023-09-22 13:51:08 +02:00
|
|
|
|
|
|
|
const loadMore = () => {
|
|
|
|
if (!$fetch.loading) {
|
|
|
|
fetch.nextPage()
|
|
|
|
}
|
|
|
|
}
|
2021-02-01 14:15:35 +01:00
|
|
|
</script>
|
|
|
|
|
2021-02-05 11:53:25 +01:00
|
|
|
<Field
|
2021-02-01 14:15:35 +01:00
|
|
|
{label}
|
|
|
|
{field}
|
2021-02-17 16:16:44 +01:00
|
|
|
{disabled}
|
2021-08-10 15:37:14 +02:00
|
|
|
{validation}
|
2023-10-10 12:22:59 +02:00
|
|
|
defaultValue={expandedDefaultValue}
|
2023-09-20 17:22:07 +02:00
|
|
|
{type}
|
2021-02-01 14:15:35 +01:00
|
|
|
bind:fieldState
|
|
|
|
bind:fieldApi
|
|
|
|
bind:fieldSchema
|
2021-05-04 12:04:42 +02:00
|
|
|
>
|
2021-04-15 20:43:18 +02:00
|
|
|
{#if fieldState}
|
2023-09-21 11:39:02 +02:00
|
|
|
<svelte:component
|
|
|
|
this={component}
|
2023-09-25 12:33:21 +02:00
|
|
|
options={enrichedOptions}
|
2023-09-21 11:39:02 +02:00
|
|
|
{autocomplete}
|
2023-09-22 11:00:56 +02:00
|
|
|
value={selectedValue}
|
2023-09-21 11:39:02 +02:00
|
|
|
on:change={multiselect ? multiHandler : singleHandler}
|
2023-09-22 13:51:08 +02:00
|
|
|
on:loadMore={loadMore}
|
2023-09-21 11:39:02 +02:00
|
|
|
id={fieldState.fieldId}
|
|
|
|
disabled={fieldState.disabled}
|
|
|
|
error={fieldState.error}
|
|
|
|
getOptionLabel={getDisplayName}
|
|
|
|
getOptionValue={option => option._id}
|
|
|
|
{placeholder}
|
2023-09-22 13:04:44 +02:00
|
|
|
bind:searchTerm
|
2023-09-22 14:03:23 +02:00
|
|
|
loading={$fetch.loading}
|
2023-09-25 12:23:17 +02:00
|
|
|
bind:open
|
2023-09-25 12:27:28 +02:00
|
|
|
customPopoverMaxHeight={400}
|
2023-09-21 11:39:02 +02:00
|
|
|
/>
|
2021-04-15 20:43:18 +02:00
|
|
|
{/if}
|
2021-02-05 11:53:25 +01:00
|
|
|
</Field>
|