Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make data persist in create forms after the closing the form without losing content #259

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions frontend/src/lib/components/Forms/AutocompleteSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@
import { formFieldProxy } from 'sveltekit-superforms/client';
import { localItems, toCamelCase } from '$lib/utils/locales';
import { languageTag } from '$paraglide/runtime';
import { onMount } from 'svelte';
import { formSubmittedStore } from '$lib/utils/stores';

export let label: string | undefined = undefined;
export let field: string;
export let helpText: string | undefined = undefined;

export let form;
export let origin: string;
export let URLModel: string;
export let multiple = false;
export let nullable = false;

export let hide = false;

const { value, errors, constraints } = formFieldProxy(form, field);
const dataSaving = origin === "create";

export let options: { label: string; value: string; suggested?: boolean }[];

Expand All @@ -28,6 +33,32 @@

$: selectedValues = selected.map((item) => item.value);

let _sessionStorage = null;
onMount(() => {
if (!dataSaving) return;
_sessionStorage = sessionStorage;
const savedData = JSON.parse(_sessionStorage.getItem("create_form_saved_data") ?? "{}");
const currentData = savedData[URLModel];
if (currentData) {
const savedValue = currentData[field];
if (savedValue) {
selected = savedValue;
}
}
});

$: if (dataSaving && _sessionStorage && !$formSubmittedStore) {
const savedData = JSON.parse(_sessionStorage.getItem("create_form_saved_data") ?? "{}");

const currentData = savedData[URLModel] ?? {};
if (!sessionStorage.hasOwnProperty(URLModel)) {
currentData[field] = selected;
}
savedData[URLModel] = currentData;

_sessionStorage.setItem("create_form_saved_data",JSON.stringify(savedData));
}

const default_value = nullable ? null : selectedValues[0];

$: ($value = multiple ? selectedValues : selectedValues[0] ?? default_value), handleSelectChange();
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/lib/components/Forms/Form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import type { AnyZodObject } from 'zod';
import { focusTrap } from '@skeletonlabs/skeleton';
import { onMount } from 'svelte';
import { formSubmittedStore } from '$lib/utils/stores';

import type { ModalStore } from '@skeletonlabs/skeleton';
import { getModalStore } from '@skeletonlabs/skeleton';
Expand All @@ -14,12 +16,15 @@

export let data: SuperValidated<AnyZodObject>;
export let dataType: 'form' | 'json';
export let origin: String;
export let URLModel: string;
export let invalidateAll = true; // set to false to keep form data using muliple forms on a page
export let validators: AnyZodObject;
export let applyAction = true;
export let resetForm = false;
export let onSubmit = (submit_data: any) => {};
export let taintedMessage: string | null = m.taintedFormMessage();
const dataSaving = origin === "create";

export let debug = false; // set to true to enable SuperDebug component

Expand All @@ -41,14 +46,28 @@
});

const { form, message /*, tainted*/, delayed, errors, allErrors, enhance } = _form;

let _sessionStorage = null;
onMount(() => {
_sessionStorage = sessionStorage;
})

$: if (URLModel) { formSubmittedStore.set(false); }
</script>

{#if debug}
<SuperDebug data={$form} />
<SuperDebug data={$errors} />
{/if}

<form method="POST" use:enhance use:focusTrap={true} {...$$restProps}>
<form method="POST" on:submit={() => {
if (dataSaving) {
formSubmittedStore.set(true);
const savedData = JSON.parse(_sessionStorage?.getItem("create_form_saved_data") ?? "{}");
savedData[URLModel] = {};
_sessionStorage?.setItem("create_form_saved_data",JSON.stringify(savedData));
}
}} use:enhance use:focusTrap={true} {...$$restProps}>
{#if $errors._errors}
{#each $errors._errors as error}
<p class="text-error-500 text-sm font-medium">{error}</p>
Expand Down
70 changes: 36 additions & 34 deletions frontend/src/lib/components/Forms/ModelForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
dataType={shape.attachment ? 'form' : 'json'}
enctype={shape.attachment ? 'multipart/form-data' : 'application/x-www-form-urlencoded'}
data={form}
{origin}
{URLModel}
let:form
let:data
let:initialData
Expand Down Expand Up @@ -97,22 +99,22 @@
/>
{/if}
{#if shape.name}
<TextField {form} field="name" label={m.name()} data-focusindex="0"/>
<TextField {form} {origin} {URLModel} field="name" label={m.name()} data-focusindex="0"/>
{/if}
{#if shape.description}
<TextArea {form} field="description" label={m.description()} data-focusindex="1"/>
<TextArea {form} {origin} {URLModel} field="description" label={m.description()} data-focusindex="1"/>
{/if}
{#if URLModel === 'projects'}
<AutocompleteSelect
{form}
{form} {origin} {URLModel}
options={getOptions({ objects: model.foreignKeys['folder'] })}
field="folder"
label={m.domain()}
hide={initialData.folder}
/>
<TextField {form} field="internal_reference" label={m.internalReference()} />
<TextField {form} {origin} {URLModel} field="internal_reference" label={m.internalReference()} />
<Select
{form}
{form} {origin} {URLModel}
options={model.selectOptions['lc_status']}
field="lc_status"
label={m.lcStatus()}
Expand All @@ -128,8 +130,8 @@
label={m.project()}
hide={initialData.project}
/>
<TextField {form} field="version" label={m.version()} />
<Select {form} options={model.selectOptions['status']} field="status" label={m.status()} />
<TextField {form} {origin} {URLModel} field="version" label={m.version()} />
<Select {form} {origin} {URLModel} options={model.selectOptions['status']} field="status" label={m.status()} />
<AutocompleteSelect
{form}
options={getOptions({ objects: model.foreignKeys['risk_matrix'] })}
Expand All @@ -151,24 +153,24 @@
field="reviewers"
label={m.reviewers()}
/>
<TextField type="date" {form} field="eta" label={m.eta()} helpText={m.etaHelpText()} />
<TextField type="date" {form} {origin} {URLModel} field="eta" label={m.eta()} helpText={m.etaHelpText()} />
<TextField
type="date"
{form}
{form} {origin} {URLModel}
field="due_date"
label={m.dueDate()}
helpText={m.dueDateHelpText()}
/>
{:else if URLModel === 'threats'}
<TextField {form} field="ref_id" label={m.ref()} />
<TextField {form} {origin} {URLModel} field="ref_id" label={m.ref()} />
<AutocompleteSelect
{form}
options={getOptions({ objects: model.foreignKeys['folder'] })}
field="folder"
label={m.domain()}
hide={initialData.folder}
/>
<TextField {form} field="provider" label={m.provider()} />
<TextField {form} {origin} {URLModel} field="provider" label={m.provider()} />
{:else if URLModel === 'risk-scenarios'}
<AutocompleteSelect
{form}
Expand All @@ -193,13 +195,13 @@
{:else if URLModel === 'applied-controls' || URLModel === 'policies'}
{#if schema.shape.category}
<Select
{form}
{form} {origin} {URLModel}
options={model.selectOptions['category']}
field="category"
label={m.category()}
/>
{/if}
<Select {form} options={model.selectOptions['status']} field="status" label={m.status()} />
<Select {form} {origin} {URLModel} options={model.selectOptions['status']} field="status" label={m.status()} />
<AutocompleteSelect
{form}
multiple
Expand All @@ -210,17 +212,17 @@
field="evidences"
label={m.evidences()}
/>
<TextField type="date" {form} field="eta" label={m.eta()} helpText={m.etaHelpText()} />
<TextField type="date" {form} {origin} {URLModel} field="eta" label={m.eta()} helpText={m.etaHelpText()} />
<TextField
type="date"
{form}
{form} {origin} {URLModel}
field="expiry_date"
label={m.expiryDate()}
helpText={m.expiryDateHelpText()}
/>
<TextField {form} field="link" label={m.link()} helpText={m.linkHelpText()} />
<TextField {form} {origin} {URLModel} field="link" label={m.link()} helpText={m.linkHelpText()} />
<Select
{form}
{form} {origin} {URLModel}
options={model.selectOptions['effort']}
field="effort"
label={m.effort()}
Expand All @@ -235,7 +237,7 @@
/>
{:else if URLModel === 'risk-acceptances'}
<TextField
{form}
{form} {origin} {URLModel}
type="date"
field="expiry_date"
label={m.expiryDate()}
Expand All @@ -244,7 +246,7 @@
{#if object.id && $page.data.user.id === object.approver}
<TextArea
disabled={$page.data.user.id !== object.approver}
{form}
{form} {origin} {URLModel}
field="justification"
label={m.justification()}
helpText={m.riskAcceptanceJusitficationHelpText()}
Expand Down Expand Up @@ -276,14 +278,14 @@
multiple
/>
{:else if URLModel === 'reference-controls'}
<TextField {form} field="ref_id" label={m.ref()} />
<TextField {form} {origin} {URLModel} field="ref_id" label={m.ref()} />
<Select
{form}
{form} {origin} {URLModel}
options={model.selectOptions['category']}
field="category"
label={m.category()}
/>
<TextField {form} field="provider" label={m.provider()} />
<TextField {form} {origin} {URLModel} field="provider" label={m.provider()} />
<AutocompleteSelect
{form}
options={getOptions({ objects: model.foreignKeys['folder'] })}
Expand All @@ -309,7 +311,7 @@
label={m.domain()}
hide={initialData.applied_controls || initialData.requirement_assessments}
/>
<TextField {form} field="link" label={m.link()} helpText={m.linkHelpText()} />
<TextField {form} {origin} {URLModel} field="link" label={m.link()} helpText={m.linkHelpText()} />
{:else if URLModel === 'compliance-assessments'}
<AutocompleteSelect
{form}
Expand All @@ -321,8 +323,8 @@
label={m.project()}
hide={initialData.project}
/>
<TextField {form} field="version" label={m.version()} />
<Select {form} options={model.selectOptions['status']} field="status" label={m.status()} />
<TextField {form} {origin} {URLModel} field="version" label={m.version()} />
<Select {form} {origin} {URLModel} options={model.selectOptions['status']} field="status" label={m.status()} />
<AutocompleteSelect
{form}
options={getOptions({ objects: model.foreignKeys['framework'] })}
Expand All @@ -343,24 +345,24 @@
field="reviewers"
label={m.reviewers()}
/>
<TextField type="date" {form} field="eta" label={m.eta()} helpText={m.etaHelpText()} />
<TextField type="date" {form} {origin} {URLModel} field="eta" label={m.eta()} helpText={m.etaHelpText()} />
<TextField
type="date"
{form}
{form} {origin} {URLModel}
field="due_date"
label={m.dueDate()}
helpText={m.dueDateHelpText()}
/>
{:else if URLModel === 'assets'}
<TextArea {form} field="business_value" label={m.businessValue()} />
<TextArea {form} {origin} {URLModel} field="business_value" label={m.businessValue()} />
<AutocompleteSelect
{form}
options={getOptions({ objects: model.foreignKeys['folder'] })}
field="folder"
label={m.domain()}
hide={initialData.folder}
/>
<Select {form} options={model.selectOptions['type']} field="type" label="Type" />
<Select {form} {origin} {URLModel} options={model.selectOptions['type']} field="type" label="Type" />
<AutocompleteSelect
disabled={data.type === 'PR'}
multiple
Expand All @@ -370,16 +372,16 @@
label={m.parentAssets()}
/>
{:else if URLModel === 'requirement-assessments'}
<Select {form} options={model.selectOptions['status']} field="status" label={m.status()} />
<TextArea {form} field="observation" label={m.observation()} />
<Select {form} {origin} {URLModel} options={model.selectOptions['status']} field="status" label={m.status()} />
<TextArea {form} {origin} {URLModel} field="observation" label={m.observation()} />
<HiddenInput {form} field="folder" />
<HiddenInput {form} field="requirement" />
<HiddenInput {form} field="compliance_assessment" />
{:else if URLModel === 'users'}
<TextField {form} field="email" label={m.email()} data-focusindex="2"/>
<TextField {form} {origin} {URLModel} field="email" label={m.email()} data-focusindex="2"/>
{#if shape.first_name && shape.last_name}
<TextField {form} field="first_name" label={m.firstName()} />
<TextField {form} field="last_name" label={m.lastName()} />
<TextField {form} {origin} {URLModel} field="first_name" label={m.firstName()} />
<TextField {form} {origin} {URLModel} field="last_name" label={m.lastName()} />
{/if}
{#if shape.user_groups}
<AutocompleteSelect
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/lib/components/Forms/Select.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import { localItems, toCamelCase } from '$lib/utils/locales';
import { languageTag } from '$paraglide/runtime';
import type { AnyZodObject } from 'zod';
import { onMount } from 'svelte';
import { formSubmittedStore } from '$lib/utils/stores';

let _class = '';

Expand All @@ -14,8 +16,11 @@
export let color_map = {};

export let form: SuperForm<AnyZodObject>;
export let origin: string;
export let URLModel: string;

const { value, errors, constraints } = formFieldProxy(form, field);
const dataSaving = origin === "create";

interface Option {
label: unknown;
Expand All @@ -26,6 +31,32 @@

$: classesTextField = (errors: string[] | undefined) =>
errors && errors.length > 0 ? 'input-error' : '';

let _sessionStorage = null;
onMount(() => {
if (!dataSaving) return;
_sessionStorage = sessionStorage;
const savedData = JSON.parse(_sessionStorage.getItem("create_form_saved_data") ?? "{}");
const currentData = savedData[URLModel];
if (currentData) {
const savedValue = currentData[field];
if (savedValue) {
value.set(savedValue);
}
}
});

$: if (dataSaving && _sessionStorage && !$formSubmittedStore) {
const savedData = JSON.parse(_sessionStorage.getItem("create_form_saved_data") ?? "{}");

const currentData = savedData[URLModel] ?? {};
if (!sessionStorage.hasOwnProperty(URLModel)) {
currentData[field] = $value;
}
savedData[URLModel] = currentData;

_sessionStorage.setItem("create_form_saved_data",JSON.stringify(savedData));
}
</script>

<div>
Expand Down
Loading
Loading