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

[Question] Handling forms #86

Open
emlautarom1 opened this issue May 27, 2020 · 2 comments
Open

[Question] Handling forms #86

emlautarom1 opened this issue May 27, 2020 · 2 comments
Labels
question Further information is requested

Comments

@emlautarom1
Copy link

Hi! First of all, I want to thank you for this awesome library. I never thought that writing UI in Haskell could be this easy.

I'm having a bit of trouble when it comes to the design and implementation of forms, and I'm unable to find a full example of this use case, so I'm not sure about how to implement it.

Currently, I have a view with a few Entries, each for a specific input - let's say name, surname, email, and a submit Button.

  • The first solution that comes to mind is defining a field in my State that holds the current value of each Entry in some kind of Map. I can get the current Text for each Entry using the onM #changed function and entryGetText, then I can fire an Event that updates the State with the new value. This approach has a few advantages like on the fly validation and works fine for a small project, but let's say that my application has 20/30 forms. Would this scale?

  • What I would like to do (don't know if this is the right way™) is to use a function that collects the text inside all my Entries and builds some kind of Map (id -> value like in HTML forms) and fires an Event, something like UserRegistrationEvent Fields, where Fields is a map of ids to values. This second approach would reduce the amount of code inside the State record but sacrifices the on the fly validation. Also, since Fields is a kind of Map I'm a little worried about safety: I need to make sure that the values that I look up inside the map are valid - for example, if I look up phone-number in my example I could get a runtime error.

Please, let me know what you think. I'll update the issue with some code snippets from my use case as soon as possible.

Also: I looked into the React-Redux ecosystem since gi-gtk-declarative uses a similar design pattern for UIs and I found redux-form. It would be interesting to have a similar library for this amazing project.
Disclaimer: I primarily use Angular for my frontend projects so I'm not very confident in porting that library to Haskell

@Dretch
Copy link
Collaborator

Dretch commented Jun 7, 2020

So far I have taken the approach of having an event (e.g. SetEmailField) for each field in each form. This is simple and clear but involves a bunch of boilerplate code. For form state I have used data types rather than a generic Map - for the type safety reasons you mention.

I would be interested in some kind of abstraction to reduce form layout / validation / event-handling boilerplate. I'd be tempted to try and copy something from the Elm universe, rather than Redux, since it is probably easier to translate from Elm rather than Javascript (but Haskell is more expressive than Elm so the gi-gtk-declarative version might be nicer). In this case https://package.elm-lang.org/packages/hecrj/composable-form/8.0.1/ looks like it might work in gi-gtk-declarative.

@Dretch Dretch added the question Further information is requested label Jun 7, 2020
@emlautarom1
Copy link
Author

Indeed, there's a lot of boilerplate that can be reduced, but, as I said, I'm not an experienced Haskell dev. Defining an event for each field is a lot of work, especially in my use case, since my forms have in total over 30 fields.

In order to solve this I defined a forms :: Forms field inside my State, and every form exists inside this field. Then, I defined a UpdateForms Forms event which replaces the current forms with the ones that comes in the event.

Now, the tricky part is updating each field for every form. I defined a FormField type that works something like the following:

data FormField src a v = FormField {
  _label :: Text,             -- Used in prompts for this field (ex: "User name")
  _value :: a,                -- The kind of value of this field. Most of the times is Text
  _setter :: src -> a -> src  -- How to update this form field given a src (ex. Forms) and a new value
} ...

Now, when I create a FormField I write a setter function, that given a Forms structure will update the field and return a new Forms structure. Also, I use the v type parameter for automatic safe parsing from a type a to v.

For example, a field userAge :: FormField Forms Text Int is a field that will know how to convert its value to an Int, how to update its value given that this field will live inside a Forms structure and will also provide a label

There's a lot of boilerplate but this was the best I could do. You can see how this works in this toy project

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants