Formless v0.2.0 released

:tada: :tada: :tada:

I’ve just released v0.2.0 of Formless, a renderless component for forms in Halogen.

This marks a major improvement to the library, and resolves all three major issues I asked for feedback on in the release post:

https://purescript-users.ml/t/call-for-feedback-formless-a-new-form-library-for-halogen/308/9

Major thank-yous to @monoidmusician, @Unisay, and @vyorkin for their help, feedback, and notes.

Next Steps

As far as I can tell, there are no blocking issues with Formless to prevent its use in production (it is in use at CitizenNet / Condé Nast and at least two other companies with production PureScript apps). I’m sure, however, that there are use cases I have missed or am not supporting adequately. If any of the below apply to you, then I’d love to hear from you!

  • Do you have a form you’re working on or have built in the past that is not represented in the Formless examples? Tell me about it, and I’ll add a new example demonstrating how to build that form with the library!
  • Have you built a form with Formless and have feedback on its usability? Tell me about it, and perhaps we can find ways to make the library friendlier and more comprehensible to users.
8 Likes

Hi! Congrats for the library!
I’ve started using it and it’s been quite straightforward so far :slight_smile:

There are a couple of things I’m missing:

  1. I have a filters form where I want to react to changes immediatly (there’s no submit).
    I’m listening to Changed message, but what I’d like is to be able to extract the complete state as a record.
    Right now I don’t have many fields, so I’m manually getting each one with: F.getInput _fooField f.form .
    It’s probably something trivial using lenses, but I have zero experience with them. It would be cool to have a F.getInputs function.

  2. validators is mandatory? What if I’m not validating any field? Is there a way to generate a “dummy” validator in that case?

Thanks!

1 Like

Thanks for taking the time to try out Formless!


I’m listening to Changed message, but what I’d like is to be able to extract the complete state as a record. It would be cool to have a F.getInputs function.

That would be cool! Unfortunately, I haven’t got anything in the library to help with this yet. There is a helper function in the library called unwrapRecord, which can be used to turn a record of newtypes into a record of the newtype’s contents. For example, here’s how it would work on a form newtype around a record of InputField:

inputs :: Form Record InputField
inputs = Form { a: InputField "hello", b: InputField 10 }

inputs' :: Form Record InputField -> { a :: String, b :: Int }
inputs' = unwrapRecord <<< unwrap

You can use this to get all your form fields as a simple record, which isn’t quite what you’re asking for (rather than { a :: String, b :: Int } you’d have:

{ a :: { input :: String, ... }
, b :: { input :: Int, ... } 
}

…but perhaps this gets you a little closer to convenience in the meantime? You can’t quite do fields.a, but you could do fields.a.input instead.


validators is mandatory? What if I’m not validating any field? Is there a way to generate a “dummy” validator in that case?

There will always be a need to provide Formless with a function to get from your input to output type for a field. That doesn’t have to be validation; you could go from an Int input to a String output with hoistFn_ show, with no possible error, for example. In the case that all the form inputs are the same as all the form outputs, I suppose I could support no validation at all and skip that processing! I’ll consider doing so for the next version, 0.3.

In the meantime, you can accomplish the same by providing hoistFn_ identity as the validator for any field that you don’t need validation on. At my company, we wrapped this up as a function called accept :: forall form m i i. Monad m => Validation form m Void i i; accept = hoistFn_ identity. That way we have to specify in our form row that a field has a Void error type so it’s explicit that it cannot fail, and of course that the input and output types are the same.

For example:

validators = MyForm
  { a: accept
  , b: hoistFn_ identity
  , c: accept
  }
1 Like

Thanks for the answer!

I ended up writing as you suggested:

extractResultFromChanged
  :: forall xs a form fields r
   . RL.RowToList fields xs
  => UnwrapRecord xs fields a
  => Newtype (form Record F.FormField) (Record fields)
  => Record (form :: form Record F.FormField | r)
  -> Record a
extractResultFromChanged = F.unwrapRecord <<< unwrap <<< _.form

Then I spent 2 hours playing with heterogeneous trying to map the record with the input fields, but I gave up :smiley:

1 Like

As an update, I got to this final code after getting some help:

extractResultFromChanged
  :: forall xs a form fields r
   . RL.RowToList fields xs
  => UnwrapRecord xs fields a
  => Newtype (form Record F.FormField) { | fields }
  => { form :: form Record F.FormField | r }
  -> { | a }
extractResultFromChanged = F.unwrapRecord <<< unwrap <<< _.form

newtype GetProp (prop :: Symbol) = GetProp (SProxy prop)

instance getProp ::
  (IsSymbol prop, Row.Cons prop a rx r) =>
  Mapping (GetProp prop) { | r } a where
  mapping (GetProp prop) = Record.get prop

getInputs' :: forall rin rout.
  HMap (GetProp "input") { | rin } { | rout } =>
  { | rin } ->
  { | rout }
getInputs' = hmap (GetProp (SProxy :: SProxy "input")) 

getInputs
   :: forall a xs form fields rout r ys
    . Newtype (form Record F.FormField) { | fields }
   => RL.RowToList fields xs
   => UnwrapRecord xs fields a
   => RL.RowToList a ys
   => HMap (GetProp "input") { | a } { | rout }
   => MapRecordWithIndex ys (ConstMapping (GetProp "input")) a rout
   => { form :: form Record F.FormField | r }
   -> { | rout }
getInputs = getInputs' <<< extractResultFromChanged

So for example then I can use it like this:

eval (HandleFormless (F.Changed f) next) = next <$ do
  H.modify_ (_ { filtering = getInputs f })
1 Like

Thanks! If you’d like to share your reasons for wanting this function and would like it to be added to the library, please consider opening an issue and/or pull request :slight_smile: