Files
fnx_web/svelte/src/util/Form.svelte

286 lines
7.2 KiB
Svelte
Raw Normal View History

2025-03-25 17:58:26 +01:00
<script lang="ts" context="module">
export type FormConfig = {
fields: FormField[],
submit_label: string
submit_red?: boolean,
on_submit: (values: {[key: string]: string}) => Promise<SubmitResult>,
}
export type FormField = {
type: string,
name?: string,
label?: string,
default_value?: string,
description?: string,
separator?: boolean,
radio_values?: string[], // Options to choose from when type is "radio"
pattern?: string, // Used for pattern matching on input fields
binding?: any
}
export type SubmitResult = {
success: boolean,
message?: string,
messages?: string[],
error_json?: GenericResponse,
}
</script>
<script lang="ts">
2021-09-21 21:39:28 +02:00
import { onMount } from "svelte";
import Spinner from "./Spinner.svelte";
2025-03-25 17:58:26 +01:00
import type { GenericResponse } from "../lib/PixeldrainAPI.mjs";
2021-09-21 21:39:28 +02:00
2025-03-25 17:58:26 +01:00
export let config: FormConfig
2021-09-21 21:39:28 +02:00
onMount(() => {
config.fields.forEach(field => {
if(field.default_value === undefined) {
field.default_value = ""
}
})
})
let loading = false
let submitted = false
2025-03-25 17:58:26 +01:00
let submit_result: SubmitResult
2024-06-26 23:28:17 +02:00
2025-03-25 17:58:26 +01:00
const submit = async (event: SubmitEvent) => {
2021-09-21 21:39:28 +02:00
loading = true
event.preventDefault()
let field_values = {}
2021-09-23 22:16:53 +02:00
config.fields.forEach(field => {
if (field.type === "radio") {
if (field.binding === undefined) {
field_values[field.name] = ""
} else {
field_values[field.name] = field.binding
}
2021-10-19 15:59:22 +02:00
} else if (field.type === "description") {
field_values[field.name] = ""
2021-09-23 22:16:53 +02:00
} else {
field_values[field.name] = field.binding.value
}
2021-09-21 21:39:28 +02:00
})
submit_result = await config.on_submit(field_values)
2025-03-25 17:58:26 +01:00
if (submit_result && submit_result.error_json) {
2021-09-21 21:39:28 +02:00
submit_result = handle_errors(submit_result.error_json)
}
submitted = true
loading = false
return false
}
2025-03-25 17:58:26 +01:00
const field_label = (field: string) => {
2021-09-21 21:39:28 +02:00
let label = ""
config.fields.forEach(val => {
if (val.name === field) {
label = val.label
}
})
return label
}
2025-03-25 17:58:26 +01:00
const handle_errors = (response: GenericResponse) => {
2021-09-21 21:39:28 +02:00
console.log(response)
let result = {success: false, message: "", messages: null}
if (response.value === "multiple_errors") {
result.messages = []
response.errors.forEach(err => {
if (err.value === "string_out_of_range") {
result.messages.push(
2025-03-25 17:58:26 +01:00
`${field_label(<string>err.extra.field)} is too long or too short.
2021-09-21 21:39:28 +02:00
It should be between ${err.extra.min_len} and
${err.extra.max_len} characters. Current length:
${err.extra.len}`
)
} else if (err.value === "field_contains_illegal_character") {
result.messages.push(
2025-03-25 17:58:26 +01:00
`Character '${err.extra.char}' is not allowed in ${field_label(<string>err.extra.field)}`
2021-09-21 21:39:28 +02:00
)
} else if (err.value === "missing_field") {
result.messages.push(
2025-03-25 17:58:26 +01:00
`${field_label(<string>err.extra.field)} is required`
2021-09-21 21:39:28 +02:00
)
} else {
result.messages.push(err.message)
}
})
} else {
result.message = response.message
}
return result
}
</script>
2025-03-25 17:58:26 +01:00
<form method="POST" on:submit={submit}>
{#if submitted && submit_result !== undefined}
2021-09-21 21:39:28 +02:00
{#if submit_result.messages}
<div id="submit_result" class:highlight_green={submit_result.success} class:highlight_red={!submit_result.success}>
Something went wrong, please correct these errors before continuing:<br/>
<ul>
{#each submit_result.messages as message}
<li>{message}</li>
{/each}
</ul>
</div>
{:else}
<div id="submit_result" class:highlight_green={submit_result.success} class:highlight_red={!submit_result.success}>
2021-09-23 22:16:53 +02:00
{@html submit_result.message}
2021-09-21 21:39:28 +02:00
</div>
{/if}
{/if}
2022-08-04 20:19:30 +02:00
<div class="form">
2021-09-21 21:39:28 +02:00
{#each config.fields as field}
2022-08-04 20:19:30 +02:00
{#if field.type !== "description"}
<label for="input_{field.name}">
{field.label}
</label>
{#if field.type === "text"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
2022-08-04 20:19:30 +02:00
type="text"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
2022-08-04 20:19:30 +02:00
{:else if field.type === "text_area"}
<textarea bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
class="form_input"
style="width: 100%; height: 10em; resize: vertical;"
>{field.default_value}</textarea>
{:else if field.type === "number"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
2022-08-04 20:19:30 +02:00
type="number"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
2022-08-04 20:19:30 +02:00
{:else if field.type === "decimal"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
2022-08-04 20:19:30 +02:00
type="number"
step="0.1"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
{:else if field.type === "datetime-local"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
type="datetime-local"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
2022-08-04 20:19:30 +02:00
{:else if field.type === "username"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
2022-08-04 20:19:30 +02:00
type="text"
autocomplete="username"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
2022-08-04 20:19:30 +02:00
{:else if field.type === "email"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
2022-08-04 20:19:30 +02:00
type="email"
autocomplete="email"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
2022-08-04 20:19:30 +02:00
{:else if field.type === "current_password"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
2022-08-04 20:19:30 +02:00
type="password"
autocomplete="current-password"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
2022-08-04 20:19:30 +02:00
{:else if field.type === "new_password"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value}"
2025-03-25 17:58:26 +01:00
pattern={field.pattern}
2022-08-04 20:19:30 +02:00
type="password"
autocomplete="new-password"
2025-03-25 17:58:26 +01:00
class="form_input"
/>
{:else if field.type === "totp"}
<input bind:this={field.binding}
id="input_{field.name}"
name="{field.name}"
value="{field.default_value?field.default_value:""}"
type="text"
autocomplete="one-time-code"
pattern={"[0-9]{6}"}
class="form_input"
/>
2022-08-04 20:19:30 +02:00
{:else if field.type === "radio"}
<div>
{#each field.radio_values as val}
<input bind:group={field.binding}
id="input_{field.name}_choice_{val}"
2021-09-23 22:16:53 +02:00
name="{field.name}"
2022-08-04 20:19:30 +02:00
value={val}
type="radio"
checked={val === field.default_value}/>
<label for="input_{field.name}_choice_{val}">{val}</label><br/>
{/each}
</div>
2021-09-21 21:39:28 +02:00
{/if}
2022-08-04 20:19:30 +02:00
{/if}
2021-09-21 21:39:28 +02:00
{#if field.description}
2022-08-04 20:19:30 +02:00
<div>
{@html field.description}
</div>
2021-09-21 21:39:28 +02:00
{/if}
{#if field.separator}
2022-08-04 20:19:30 +02:00
<hr/>
2021-09-21 21:39:28 +02:00
{/if}
{/each}
<!-- Submit button -->
2022-08-04 20:19:30 +02:00
{#if config.submit_red}
<button type="submit" class="button_red">{@html config.submit_label}</button>
{:else}
<button type="submit" class="button_highlight">{@html config.submit_label}</button>
{/if}
</div>
2023-05-27 15:50:44 +02:00
{#if loading}
<div class="spinner_container">
<Spinner></Spinner>
</div>
{/if}
2021-09-21 21:39:28 +02:00
</form>
<style>
.spinner_container {
position: absolute;
top: 10px;
2023-05-27 15:50:44 +02:00
right: 10px;
2021-09-21 21:39:28 +02:00
height: 100px;
width: 100px;
}
</style>