Closed liamcurry closed 5 years ago
Ok so my current solution for this isn't pretty but it works. Maybe this can be improved, please let me know if you have suggestions:
field
type I have three fields related to options: Option(int)
, AddOption
, and DelOption(int)
, where the int
variables are the indexes in the array.get
function only the Option(int)
field returns an actual value, as the other two are just dummy fields to manage state.update
field Option(int)
sets the string value, AddOption
adds a new blank value to the end of the array, and DelOption(int)
deletes the element at the index.Option(int)
field. To get around that I have to create a new validator for every possible index in the array.Here is most of the relevant code:
type value =
| String(string)
| NoValue;
type field =
| Title
| Option(int)
| AddOption
| DelOption(int);
let get = (field, state) =>
switch (field) {
| Title => String(state.title)
| Option(index) =>
state.options
|. Belt.Array.get(index)
|. Belt.Option.getWithDefault("")
|. String
| DelOption(_index) => NoValue
| AddOption => NoValue
};
let baseValidators =
Formality.(
Validators.empty
|> Validators.add(
Title,
{
strategy: Strategy.OnFirstChange,
dependents: None,
validate: isRequired,
},
)
);
let validators =
Formality.(
Belt.Array.range(0, 100)
|. Belt.Array.reduce(baseValidators, (result, i) =>
result
|> Validators.add(
Option(i),
{
strategy: Strategy.OnFirstChange,
dependents: None,
validate: isShorterThan(50),
},
)
)
);
let update = ((field, value), state) =>
switch (field, value) {
| (Option(index), String(value)) =>
state.options |. Belt.Array.set(index, value) |> ignore;
state;
| (AddOption, _value) =>
state.options |> Js.Array.push("") |> ignore;
state;
| (DelOption(index), _novalue) => {
...state,
options: state.options |> Js.Array.filteri((_o, i) => i != index),
}
| _ => failwith("Config.update function received bad input")
};
/* Later on in the render function... */
<ol>
(
form.state.options
|> Array.mapi((i, o) =>
<li key=(i |> string_of_int)>
<input
value=(
form.state.options
|. Belt.Array.get(i)
|. Belt.Option.getWithDefault("")
)
onChange=(
event =>
event
|> Formality.Dom.toValueOnChange
|. Form.String
|> form.change(Form.Option(i))
)
/>
(
i >= 2 ?
<a
onClick=(
_event =>
form.change(DelOption(i), NoValue)
)>
("[-]" |> ReasonReact.string)
</a> :
ReasonReact.string("")
)
(
switch (Form.Option(i) |> form.results) {
| Some(Invalid(message)) =>
<div className="failure">
(message |> ReasonReact.string)
</div>
| Some(Valid) =>
<div className="success">
({j|✓|j} |> ReasonReact.string)
</div>
| None => ReasonReact.null
}
)
</li>
)
|> ReasonReact.array
)
</ol>
<a onClick=(_event => form.change(AddOption, NoValue))>
("Add option" |> ReasonReact.string)
</a>
Hey, thanks for kind words! TBH I haven't tried to implement it yet and I'd started poking around by changing value
type to something like this:
type value =
| String(string)
| ArrayOfStrings(array(string));
If your solution works I'd leave it like this, for now. ATM I'm focused on shipping 1.0.0
of re-dnd and then I want to make one more attempt to couple value
type w/ specific field
(instead of making it abstract variant). I will keep in mind this use case during prototyping and try to come up w/ some built-in solution.
Let's continue in #43
First of all thank you @alexfedoseev for creating this, it's the best form library I've seen for ReasonML so far.
I'm working on a form that has a field that is an array of strings, where options can be dynamically added and removed. There should be between 2-30 items in the array, and each string should be between 1-140 characters.
This seems like it should be possible, but I'm wondering if you could provide some guidance/examples of how to accomplish this with Formality? Thanks again.