Brady Ouren

A Purescript Form

What follows is a further look at a portion of this post by @parsonsmatt. WForm, as explained there, is a nice abstraction which is inspired partly by Yesod forms. I found it useful and dropped the implementation in a repo to work on when I have time (please help).

Right now it’s specific to Halogen and bootstrap css, but I don’t see any reason it needs to be. In essence it comes down to 2 type aliases and 1 data type. Along with some helper methods for creating fields, you can get a long way with these basics.

There are also further details around the following example in a related post about codegen

type WForm v f a =
    (Tuple a (FormAction f a))
    (Writer (Array (HTML v (f Unit))))

type FormAction f a = FormInput a -> Unit -> f Unit
data FormInput a
  = Submit
  | Edit (a -> a)

This provides a nice, general interface for forms!

Using this abstraction, we can define an Input for a component

data Input a
  = NewCurator (Form.FormInput CuratorForm) a

One thing I like about this is the ease of edit actions using the lens’ provided by codegen and the FormInput’s Edit constructor

For example, you might handle a form edit in your eval loop similar to this:

eval (NewCurator ev next) = handleNewCurator ev $> next
    handleNewCurator (Form.Edit f) = do
      H.modify (_form %~ f)
    -- ...

We can apply the edit action directly on the form via our lens’. The errors are handled internally to the WForm writer monad.

-- we need access to the form from the top level
-- the form itself already has lens' defined (src/App/Form.purs)
_form :: Lens' State CuratorForm
_form = lens _.form _ { form = _}

initialState :: State
initialState = 
  { form: CuratorForm { invitee: "", message: Nothing } }

Between WForm and our generated lens’ we can compose a form quite cleanly:

-- example of a generated field accessor for out form
_message = _Newtype <<< prop (SProxy :: SProxy "message")

render :: State -> H.ComponentHTML Input
render state =
  div_ $ Form.renderForm state.form NewCurator do
    void $ Form.textField "email" "Email" _invitee (Form.nonBlank <=< Form.emailValidator)
    Form.textFieldOpt "message" "A Note" _message  Form.optional
    --                name,      label,  lens,     validations

Some context (and all the flaws) of this example can be found here

If you think this could be useful and could provide documentation, examples, or code go to github