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

Dynamic forms options? #29

Closed
liamcurry opened this issue Jun 30, 2018 · 3 comments
Closed

Dynamic forms options? #29

liamcurry opened this issue Jun 30, 2018 · 3 comments

Comments

@liamcurry
Copy link

liamcurry commented Jun 30, 2018

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.

@liamcurry
Copy link
Author

liamcurry commented Jul 1, 2018

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:

  • In my 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.
  • In my get function only the Option(int) field returns an actual value, as the other two are just dummy fields to manage state.
  • In my 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.
  • Validators need to have a field value so they don't play very well with the extra parameters in the 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>

@alex35mil
Copy link
Member

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.

@alex35mil
Copy link
Member

Let's continue in #43

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants