Questions about validating fields in Halogen Formless

I need some help with 2 issues in Halogen Formless.

First issue:
I have a form with an optional Boolean field that requires no validation.
When I run the app in the browser, I see state.validity as Incomplete and only when the field is touched the state.validity updates to Valid.
Since the form only contains one optional field, I expect to see the form to be in Valid state, by default, without touching field.

type MyType = 
    { processTask   :: Boolean
    }
...
newtype Form r f = Form (r
    (  processTask   :: f Void Boolean Boolean
    ))
 ...   
{ validators: Form  { processTask:  F.noValidation
                    }
...                    
render state =
    HH.div_
        [ HH.input
            [ HP.type_ HP.InputCheckbox
            , HP.checked $ F.getInput _processTask state.form
            , HE.onChecked $ Just <<< F.set _processTask
            ]
        , HH.label_ [ HH.text " Create or update task" ]
        , HH.text $ show state.validity
        ]

Here is a basic working example https://try.purescript.org/?gist=8df8ae56384b516c114fcb5a562f5d45

The second issue:
In the same form above, I added a FileUpload field with a validation that checks if the user has selected a file or not.
The compiler complains about Eq typeclass is not implemented for type File.

import Web.File.File (File)

type MyType = 
    { processTask   :: Boolean
    , selectedFile  :: File
    }

newtype Form r f = Form (r
    (  processTask   :: f Void      Boolean Boolean
    ,  selectedFile  :: f V.Exists  File    File
    ))

I’m not sure if this issue has anything to do with Halogen Formless, but is there a way to validate a field of type File?

Thanks

2 Likes

As far as the first issue goes you can call modifyValidate on your optional fields when the form initializes. You’d add an Initialize action like ordinary Halogen, and when you evaluate it you can do:

-- A helper function that calls `F.handleAction` with your custom `handleAction` and `handleEvent`,
-- which in your case was `F.raiseResult`.
eval = F.handleAction handleAction F.raiseResult

handleAction = case _ of
  Initialize ->
    -- this will preserve the field's value, but run the validation on
    -- it, leaving it in a 'touched' state.
    eval $ F.modifyValidate _processTask identity

I’ll admit this is a pretty clunky way to do things, though it will allow you to guard the submit button on valid state. The reason Formless doesn’t automatically pick up optional fields is that Formless doesn’t run your validation until a field is touched (or the form is submitted), so it doesn’t know whether a field is valid or not until then.


As far as issue 2: Formless requires an Eq instance for the input type so that it can track if the field is dirty (if it’s been changed from its original state).

There’s an existing issue for file uploads in Formless. That has some information that can help you, but please also feel free to add to that issue. Ultimately this is a flaw with how Formless works, and there is an issue to migrate to Halogen Hooks which should help make some of these rough edges better.

Follow-on question: is there a nice way to generically modifyValidate all fields?

Context: I have a form representing an edit of an existing record. User should be able to edit just one field, or none at all, and submit.

Also of note, the eval listed above gave strange compile errors. Using an explicit parameter as per the external-components example fixed things:

eval act = F.handleAction handleAction (const (pure unit)) act

If you want to modify and validate all fields in the form, you can do that with modifyValidateAll:

You can also call variants like validateAll, setAll, and so on. Does this help?

1 Like

I did try this …

      eval $ F.modifyValidateAll
        { mode: identity
        , address: identity
        , gateway: identity
        , dns: identity
        , ntp: identity
        }

… but it does not compile:

Error found:
in module SystemPage.NetworkEdit
at src/SystemPage/NetworkEdit.purs:130:14 - 136:10 (line 130, column 14 - line 136, column 10)

  No type class instance was found for
                                                                       
    Prim.RowList.RowToList ( address :: forall a t. Category a => a t t
                           , dns :: forall a t. Category a => a t t    
                           , gateway :: forall a t. Category a => a t t
                           , mode :: forall a t. Category a => a t t   
                           , ntp :: forall a t. Category a => a t t    
                           )                                           
                           t6                                          
                                                                       
  The instance head contains unknown type variables. Consider adding a type annotation.

For reference, doing each field individually with F.modifyValidate works fine.

In any case, I was aiming for a generic solution that would work for any record type without having to list the fields, something akin to sequenceRecord that you showed me yesterday. I’ll ultimately need to do a lot of dialog boxes like this, so I’m seeking to put some nice generic infrastructure together. Another option for me is to patch Formless to bend it to my will.

You can from time to time run into issues with polymorphic types in records; I’m curious what happens if you do an explicit function instead? ie.

eval $ F.modifyValidateAll 
  { mode: \x -> x
  , address: \x -> x
  , gateway: \x -> x
  , ...
  }

If you are going to be writing functions that operate on arbitrary records (as with sequenceRecord), I suspect you’ll have a much better time using https://github.com/natefaubion/purescript-heterogeneous instead of writing everything from scratch yourself with Cons / RowToList.

Thanks, the explicit function did indeed compile.

I do need to get a handle on row types and heterogeneous; that stuff is still magic to me. Thanks for all your help and pointing me in a good direction.

2 Likes