You're absolutely correct that the current design needs to be fixed,
and I'm very glad you've taken the time to address it.
> I'd like to see support for the following situations in applicative
> forms:
>
> 1. Simple (pure) validation, for example a ranged integer field
>
> 2. Advanced validation: A date field that allows only dates in the
> future, a username field that only allows usernames that aren't
> already taken.
It seems the only difference between these two is that (2) involves
monadic actions, right?
> 3. Form consistency: Are the contents of the two password fields the
> same? Is the selected payment option compatible with the shipping
> option?
>
> I think none of these is properly supported by yesod-form:
>
> 1. While it's possible to do this with fieldParse, I think it's not
> the right place to do so. The fieldParse method is to convert the user
> input to the appropriate Haskell value, additional restrictions should
> be placed somewhere else, in a more composable way (usually these
> restriction don't have much to do with the parser)
I disagree here, I think fieldParse is exactly the place for this. You
can say it's not part of parsing, but then I'd just say we need to
rename fieldParse to something else. As for composable, it seems
pretty straight-forward to be able to string together a bunch of
"Either ParseError a" functions (it's just the Monad instance for
Either).
> 2. It doesn't seem possible to to this with the current version of
> yesod-form, however if support for explicit validators (item 1.) is
> added, this can be supported if they have the correct type.
If we go in the direction of using fieldParse for this, we could just
change fieldParse to return a value inside the monad.
> 3. More difficult, I don't have code for this at the moment. Could
> probably be done by adding validators for the result type of the whole
> form to fields. The field to which the validator is added could
> determine the placement of the error message.
I've never been able to think of a good way of placing the error
messages for these. And I don't just mean from a programming
perspective; there's no clear UI rule for where these kinds of things
should go. I tend towards putting these messages outside of the form
entirely. Though specifically in the case of passwords, I think it's
well accepted to put the error message on the first password field.
> I have some preliminary code to support 1. and 2. at: http://hpaste.org/49708
>
> The key is the new Validator msg m a = a -> m (Maybe msg) type. A
> Validator returns Nothing if the field is ok, an error message
> otherwise (the Maybe type is a bit counterintuitive for this in my
> opinion). The VFieldSettings type gets a Maybe Validator field
>
> I had hoped to do this by reimplementing just a few functions, but in
> the end almost all of the yesod-form code for this is duplicated.
I think if we (1) change the signature for fieldParse and (2) change
Validator to a -> m (Either msg a), it will work. This also allows us
to double-up validators and modifiers.
Can you clarify the point here? I don't think I follow.
I think you definitely have the right idea here, though if possible
I'd like to avoid introducing yet another type of form. Let's see if
we can work what you've done back into what's already there.
Michael
If anyone wants to take a crack at this, feel free.
Michael
Look at the most recent commit. I've added a "SomeMessage" existential
type that allows for multiple message types to be used. I'll cover it
in more depth in a blog post (soon hopefully).
> 2. As for composition, I like it better when the composition of
> validators is still a validator. If you move the validator into the
> parser, then you could have for example a rangedIntField and an
> oddIntField, but you couldn't combine them to make a
> rangedOddIntField, unless you had access to the parser and validator
> separately. Always having the validator separately gives you more
> options for combining it with other validators, for example if you
> would add validation rules to the models, then this gives you the
> option of running the model validator before or after the extra form
> validator fields added by the user. With a Monoid instance for the
> error messages, you could also combine two validators and collect both
> error messages. (Of course this doesn't really require a separate
> place for storing the validator, you could try to combine them with
> the parser as late as possible, but this again raises the issue with
> the error message types)
>
> That said, it might still be useful to have the parser in the IO or
> other monad, some field types might require it to properly parse a
> field (for example to read xml schema's)
I don't think we really disagree here. I'm not suggesting we stick
these two concepts together in the library, just that we don't need to
create a whole new mechanism to support it. If you look at the code I
pushed recently, the idea is that we have "check" and "checkM" to
apply validators to fields. We can define a Validator as "type
Validator a = a -> Either SomeMessage a".
>> > Actually, this form shows another limitation of yesod-form: If the
>> > user already exists (p /= Nothing), then the formlet is different: It
>> > just displays the username, the user cannot change it anymore after
>> > the first time. This means that when creating a profile, the new form
>> > should be displayed, while the current POST data is for the old one.
>> > Therefore, the POST data must be ignored. I believe the same problem
>> > occurs when running a multi-step (wizard style) form.
>>
>> Can you clarify the point here? I don't think I follow.
>
> I'll try to clarify the example: I have a form for a user profile,
> where the form that needs to be displayed after the profile has been
> created is different (the username can only be chosen when the user
> creates his account, it cannot be changed afterwards). A simplified
> version of this idea can be found here:
> http://hpaste.org/49821
>
> This is a two-step form: Once the form for Page1 has been submitted
> and validated, the application saves the value (in a session in this
> example, in the database in a real application) and displays the form
> for Page2. With the above code, this (line 49 and 50) goes wrong,
> because it runs the Page2 formlet while there is already POST form
> data for Page1. Therefore the default Page2 values are never
> displayed, and the submitted Page1 form data is incorrectly inserted
> into the Page2 form fields.
>
> regards,
>
> Luite
OK, I think I get it now. Basically, you're looking for a
"generateFormPost" as had been discussed a few days ago, right?
Michael
I just push a commit to Git containing generateForm(Get|Post), should
be ready for testing.
Michael