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

Fix/condition-input-stores #291

Merged
merged 4 commits into from
Jul 2, 2024
Merged
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
4 changes: 2 additions & 2 deletions docs/blog/posts/NextRelease.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ This is a very exciting release for me, because it includes one of the features

Now the big feature:

## **Dependent fields**
## **Conditional fields**

As with every new feature, I like to start small, so this first version is very simple.
It just settles the basic foundation and works only with the most basic field types.
This will allow me to gather feedback and improve it in the next releases after making sure that the basic functionality is working as expected.

In this first approach there are not many safeguards either, so you can end up in forms that don't render anything, for example because of with fields that are excluding each other. I don't think this is going to be a big problem in practice, but I will be monitoring the feedback to see if it is necessary to add some kind of validation, or at least some kind of warning.
In this first approach there are not many safeguards either, so you can end up in forms that don't show any field, for example because of fields that are excluding each other. I don't think this is going to be a big problem in practice, but I will be monitoring the feedback to see if it is necessary to add some kind of validation, or at least some kind of warning.
The reason I am not adding it any limitations in this first version is because flexibility: forms can be called with parameters to omit fields, default values, etc. and I don't want to limit that flexibility.

Here are some screenshots of the feature in action.
Expand Down
12 changes: 10 additions & 2 deletions src/core/input/dependentFields.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Str } from "@std";
import * as Eq from "fp-ts/Eq";
import { absurd } from "fp-ts/function";
import * as v from "valibot";
import { FieldDefinition } from "../formDefinition";
Expand All @@ -22,6 +24,12 @@ export const ConditionSchema = v.union([isSet, booleanValue, startsWith, above])
export type Condition = v.Output<typeof ConditionSchema>;
export type ConditionType = Condition["type"];

export const ConditionEq = Eq.struct({
dependencyName: Str.Eq,
type: Str.Eq,
value: Str.Eq,
});

export function availableConditionsForInput(input: FieldDefinition["input"]): ConditionType[] {
switch (input.type) {
case "text":
Expand All @@ -30,10 +38,10 @@ export function availableConditionsForInput(input: FieldDefinition["input"]): Co
case "folder":
case "note":
case "tel":
return ["startsWith", "endsWith", "isExactly", "contains"];
return ["isSet", "startsWith", "endsWith", "isExactly", "contains"];
case "slider":
case "number":
return ["above", "aboveOrEqual", "below", "belowOrEqual", "exactly"];
return ["isSet", "above", "aboveOrEqual", "below", "belowOrEqual", "exactly"];
case "toggle":
return ["boolean"];
case "date":
Expand Down
18 changes: 3 additions & 15 deletions src/std/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { BaseSchema, Output, ValiError, parse as parseV } from "valibot";
export type Option<T> = _O.Option<T>;
export type { Either, Left, Right } from "fp-ts/Either";
export type { NonEmptyArray } from "fp-ts/NonEmptyArray";
export * as O from "fp-ts/Option";
export * as Str from "fp-ts/string";
export * as Struct from "fp-ts/struct";
export * as A from "./Array";
export const flow = f;
export const pipe = p;
Expand Down Expand Up @@ -58,21 +61,6 @@ export const E = {
fold: match,
};

export const O = {
map: _O.map,
getOrElse: _O.getOrElse,
some: _O.some,
none: _O.none,
fold: _O.fold,
fromNullable: _O.fromNullable,
chain: _O.chain,
fromPredicate: _O.fromPredicate,
isNone: _O.isNone,
isSome: _O.isSome,
alt: _O.alt,
match: _O.match,
};

export const parse = tryCatchK(parseV, (e: unknown) => e as ValiError);

type ParseOpts = Parameters<typeof parse>[2];
Expand Down
2 changes: 1 addition & 1 deletion src/views/FormBuilder.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@
</div>

{#if ["text", "email", "tel", "number", "note", "tag", "dataview", "multiselect"].includes(field.input.type)}
<FormRow label="Make required" id={`required_${index}`}>
<FormRow label="Required" id={`required_${index}`}>
<Toggle bind:checked={field.isRequired} tabindex={index} />
</FormRow>
{/if}
Expand Down
121 changes: 43 additions & 78 deletions src/views/components/FormBuilder/ConditionInput.svelte
Original file line number Diff line number Diff line change
@@ -1,72 +1,44 @@
<script lang="ts">
import { input } from "@core";
import { absurd } from "fp-ts/lib/function";
import { A, O, pipe } from "@std";
import { FieldDefinition } from "src/core/formDefinition";
import FormRow from "../FormRow.svelte";
import { getInitialInputValues, makeModel } from "./ConditionInput";

export let siblingFields: FieldDefinition[];
export let name: string;
export let value: input.Condition;
let selectedTargetField: FieldDefinition | undefined = undefined;
let selectedCondition: input.ConditionType | undefined = undefined;
let textValue = "";
let numberValue = 0;
let booleanValue = false;
$: conditions =
// Why? to trigger svelte reactivity on fields changes
siblingFields && selectedTargetField
? input.availableConditionsForInput(selectedTargetField.input)
: [];
export let condition: input.Condition;
export let onChange: (condition: input.Condition) => void;
const model = makeModel(siblingFields, condition, onChange);
$: conditions = model.conditionTypeOptions;
$: conditionType = model.conditionType;
$: dependencyName = model.dependencyName;
$: valueField = model.valueField;
$: console.log(name, $conditions, $dependencyName, $valueField);
let { booleanValue, numberValue, textValue } = getInitialInputValues(condition);
$: {
if (selectedTargetField?.input.type === "toggle") {
selectedCondition = "boolean";
}
}
$: {
if (selectedCondition && selectedTargetField) {
const field = selectedTargetField?.name;
switch (selectedCondition) {
case "isSet":
value = {
dependencyName: field,
type: "isSet",
};
break;
case "boolean":
value = { dependencyName: field, type: "boolean", value: booleanValue };
break;
case "startsWith":
case "contains":
case "endsWith":
case "isExactly":
value = {
dependencyName: field,
type: selectedCondition,
value: textValue,
};
break;
case "above":
case "below":
case "aboveOrEqual":
case "belowOrEqual":
case "exactly":
value = {
dependencyName: field,
type: selectedCondition,
value: numberValue,
};
break;
default:
absurd(selectedCondition);
}
pipe(
$conditions,
O.chain((conditions) =>
// if the current condition is not in the list of available conditions, set it to the first available condition
conditions.includes($conditionType) ? O.none : A.head(conditions),
),
O.map((newVAlue) => {
$conditionType = newVAlue;
}),
);
if (O.isSome($valueField)) {
if ($valueField.value.type === "dropdown") $valueField.value.set(booleanValue);
if ($valueField.value.type === "text") $valueField.value.set(textValue);
if ($valueField.value.type === "number") $valueField.value.set(numberValue);
}
}
</script>

<FormRow label="When field" id="sibling-field-of-{name}">
<select bind:value={selectedTargetField} class="dropdown">
<select bind:value={$dependencyName} class="dropdown">
{#each siblingFields as field}
<option value={field}
<option value={field.name}
>{field.name}
{#if field.label}
({field.label})
Expand All @@ -76,36 +48,29 @@
</select>
</FormRow>

{#if conditions.length > 0}
{#if O.isSome($conditions)}
<FormRow label="Condition" id="condition-{name}">
{#if selectedTargetField?.input.type === "toggle"}
<select class="dropdown" disabled>
<option value="boolean">is</option>
</select>
{:else}
<select bind:value={selectedCondition} class="dropdown">
{#each conditions as condition}
<option value={condition}>
{condition}
</option>
{/each}
</select>
{/if}
<select class="dropdown" bind:value={$conditionType}>
{#each $conditions.value as option}
<option value={option}>
{option}
</option>
{/each}
</select>
</FormRow>
{/if}
{#if selectedCondition && selectedCondition !== "isSet"}
{#if O.isSome($valueField)}
<FormRow label="Value" id="condition-value-{name}">
{#if selectedCondition === "contains" || selectedCondition === "startsWith" || selectedCondition === "endsWith" || selectedCondition === "isExactly"}
{#if $valueField.value.type === "text"}
<input type="text" class="input" bind:value={textValue} />
{:else if selectedCondition === "above" || selectedCondition === "below" || selectedCondition === "aboveOrEqual" || selectedCondition === "belowOrEqual" || selectedCondition === "exactly"}
{:else if $valueField.value.type === "number"}
<input type="number" class="input" bind:value={numberValue} />
{:else if selectedCondition === "boolean"}
<select bind:value={booleanValue} class="dropdown">
<option value={false}>False</option>
<option value={true}>True</option>
{:else if $valueField.value.type === "dropdown"}
<select class="dropdown" bind:value={booleanValue}>
{#each $valueField.value.options as option}
<option value={option}>{option}</option>
{/each}
</select>
{:else}
{absurd(selectedCondition)}
{/if}
</FormRow>
{/if}
Loading
Loading