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

190 lines
4.7 KiB
Svelte
Raw Normal View History

<script>
2020-05-07 11:53:34 +02:00
import { onMount } from "svelte"
import { fade } from "svelte/transition"
import {
Label,
DatePicker,
Input,
Select,
Button,
Toggle,
} from "@budibase/bbui"
2020-09-23 17:15:09 +02:00
import Dropzone from "./attachments/Dropzone.svelte"
import LinkedRecordSelector from "./LinkedRecordSelector.svelte"
import debounce from "lodash.debounce"
import ErrorsBox from "./ErrorsBox.svelte"
import { capitalise } from "./helpers"
export let _bb
2020-05-07 23:15:09 +02:00
export let model
export let title
export let buttonText
const TYPE_MAP = {
string: "text",
boolean: "checkbox",
2020-06-29 20:55:27 +02:00
number: "number",
}
const DEFAULTS_FOR_TYPE = {
string: "",
boolean: false,
number: null,
link: [],
}
let record
2020-05-07 23:15:09 +02:00
let store = _bb.store
2020-06-03 17:10:03 +02:00
let schema = {}
let modelDef = {}
let saved = false
let recordId
let isNew = true
let errors = {}
2020-05-07 23:15:09 +02:00
$: fields = schema ? Object.keys(schema) : []
2020-06-03 17:10:03 +02:00
$: if (model && model.length !== 0) {
fetchModel()
}
async function fetchModel() {
2020-06-18 17:59:31 +02:00
const FETCH_MODEL_URL = `/api/models/${model}`
2020-06-03 17:10:03 +02:00
const response = await _bb.api.get(FETCH_MODEL_URL)
modelDef = await response.json()
schema = modelDef.schema
record = {
modelId: model,
}
2020-06-03 17:10:03 +02:00
}
2020-05-07 23:15:09 +02:00
const save = debounce(async () => {
for (let field of fields) {
// Assign defaults to empty fields to prevent validation issues
2020-09-23 17:15:09 +02:00
if (!(field in record)) {
record[field] = DEFAULTS_FOR_TYPE[schema[field].type]
2020-09-23 17:15:09 +02:00
}
}
2020-06-18 17:59:31 +02:00
const SAVE_RECORD_URL = `/api/${model}/records`
const response = await _bb.api.post(SAVE_RECORD_URL, record)
2020-05-18 12:01:09 +02:00
const json = await response.json()
2020-05-07 23:15:09 +02:00
if (response.status === 200) {
store.update(state => {
state[model] = state[model] ? [...state[model], json] : [json]
return state
})
2020-07-08 17:31:26 +02:00
errors = {}
// wipe form, if new record, otherwise update
2020-07-08 17:31:26 +02:00
// model to get new _rev
record = isNew ? { modelId: model } : json
2020-07-06 18:10:55 +02:00
// set saved, and unset after 1 second
// i.e. make the success notifier appear, then disappear again after time
saved = true
setTimeout(() => {
saved = false
}, 3000)
}
2020-05-07 23:15:09 +02:00
if (response.status === 400) {
errors = Object.keys(json.errors)
.map(k => ({ dataPath: k, message: json.errors[k] }))
.flat()
}
})
onMount(async () => {
const routeParams = _bb.routeParams()
2020-07-08 17:31:26 +02:00
recordId =
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
isNew = !recordId || recordId === "new"
if (isNew) {
record = { modelId: model }
return
2020-07-08 17:31:26 +02:00
}
const GET_RECORD_URL = `/api/${model}/records/${recordId}`
const response = await _bb.api.get(GET_RECORD_URL)
record = await response.json()
2020-07-08 17:31:26 +02:00
})
</script>
2020-06-03 23:49:55 +02:00
<form class="form" on:submit|preventDefault>
{#if title}
<h1>{title}</h1>
{/if}
2020-06-03 23:49:55 +02:00
<div class="form-content">
<ErrorsBox {errors} />
2020-05-07 23:15:09 +02:00
{#each fields as field}
{#if schema[field].type === 'options'}
<Select
secondary
label={capitalise(schema[field].name)}
bind:value={record[field]}>
<option value="">Choose an option</option>
{#each schema[field].constraints.inclusion as opt}
<option>{opt}</option>
{/each}
</Select>
{:else if schema[field].type === 'datetime'}
<DatePicker
label={capitalise(schema[field].name)}
bind:value={record[field]} />
{:else if schema[field].type === 'boolean'}
<Toggle
text={capitalise(schema[field].name)}
bind:checked={record[field]} />
{:else if schema[field].type === 'number'}
<Input
label={capitalise(schema[field].name)}
type="number"
bind:value={record[field]} />
{:else if schema[field].type === 'string'}
<Input
label={capitalise(schema[field].name)}
bind:value={record[field]} />
{:else if schema[field].type === 'attachment'}
<div>
<Label extraSmall grey>{schema[field].name}</Label>
2020-09-23 17:15:09 +02:00
<Dropzone bind:files={record[field]} />
</div>
{:else if schema[field].type === 'link'}
<LinkedRecordSelector
secondary
bind:linkedRecords={record[field]}
schema={schema[field]} />
{/if}
2020-05-07 23:15:09 +02:00
{/each}
<div class="buttons">
<Button primary on:click={save} green={saved}>
{#if saved}Success{:else}{buttonText || 'Submit Form'}{/if}
</Button>
2020-06-03 23:49:55 +02:00
</div>
</div>
</form>
<style>
2020-06-03 23:49:55 +02:00
.form {
display: flex;
flex-direction: column;
2020-06-03 23:49:55 +02:00
align-items: center;
}
.form-content {
margin-bottom: var(--spacing-xl);
display: grid;
gap: var(--spacing-xl);
width: 100%;
2020-06-03 23:49:55 +02:00
}
.buttons {
2020-06-03 23:49:55 +02:00
display: flex;
justify-content: flex-end;
}
</style>