gi-gtk-declarative icon indicating copy to clipboard operation
gi-gtk-declarative copied to clipboard

[Question] Handling forms

Open emlautarom1 opened this issue 4 years ago • 2 comments

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

emlautarom1 avatar May 27 '20 22:05 emlautarom1

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 avatar Jun 07 '20 11:06 Dretch

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

emlautarom1 avatar Jun 09 '20 20:06 emlautarom1