Form building libraries have always been a trade-off… They are powerful and can be wonderful in result but in any non-trivial implementation they take so much configuration and such that it ends up just being another (slow) mini-language on top of the template’s mini language on top of Perl and probably with a bunch of JS, and now HTML5 extensions, in the mix.
A well behaved model on the backend should do its own validation—so I say—and that means another layer of validation which is not DRY and is ripe for bugs by schism. I was a sometimes fan of them but ended up drifting away and never missed it.
I don’t have a best practice recommendation but I will say that backend based form building libraries are probably a mistake unless they are integral to the full framework and there is nothing like that in Perl. More and more in JS… If you really want to use a form builder, CSS is extremely powerful and fairly x-browser reliable today. I would still push validation to the model/data layer. It’s the most natural and reusable place for it. Also, I would never use the input pattern property. It is invisible to the actual victim user of the constraint so it’s not good UX and it surfaces your code or expectations to hackers and the client side checks can be bypassed with direct requests/POSTs and regular expressions can be exploited maliciously.
Update: also be careful. ALL user supplied data must be escaped. It’s opt in in TT2. So, anything that comes from params, etc, needs something like: [% name | html %]
Update: typoeses.
| [reply] [d/l] |
A well behaved model on the backend should do its own validation
Yes, I will do the validation in the backend, especially since the web app is going to be a front-end for a fairly fragile C++ executable. (The original design was to glue this executable with some C CGI library; we have since decided to use a real web framework.)
I would still push validation to the model/data layer. It’s the most natural and reusable place for it.
Agreed. What also worries me is the correspondence between <input name="..."> tags in the form layout and the expectations of the model (query_parameters->get("...") in Dancer terms). Is there a way to enforce it, besides form generation and various forms of testing? I can easily imagine a typo squeezing in one of the rarely used parameters. Making it relatively painless to add new parameters would also be nice.
Also, I would never use the input pattern property. It is invisible to the actual user of the constraint so it’s not good UX and it surfaces your code or expectations to hackers and the client side checks can be bypassed with direct requests/POSTs and regular expressions can be exploited maliciously.
Noted. Does it mean that good client-side validation has to be done in JavaScript? I'm only doing client-side validation for the sake of the user ("prevent errors instead of presenting error messages after the error happened"), but I have personal aversion to client-side scripting (which I can overcome if the job needs it). For most fields, I would be okay with the client being able to verify that a field contains a floating-point number, but browsers are not up to it yet.
ALL user supplied data must be escaped. It’s opt in in TT2. So, anything that comes from params, etc, needs something like: [% name | html %]
Thanks for the reminder. I omitted that in the OP for simplicity, since I was typing code from my head, not having any form backend code yet.
| [reply] [d/l] [select] |
You can and should do some validation on the page in the user's browser, but as noted the real bulwark is the backend. On the other hand you can do it very close to the request arrival so your app doesn't really fire up unless needed. Since you are (rightly) concerned about strict param validation and it sounds like a serious app, I would start out right and create an OpenAPI spec for the app defining the routes and the acceptable params. There are plugins for Dancer which will build the whole app from the spec! And then validate the input if desired, etc. There's no reason to wait until the data is received by a route handler to validate it.
The way forward always starts with a minimal test.
| [reply] |
Hi, not 100% sure what "how should I structure the form description if I have to account for various deviations from the template" means, but since you are in Dancer2 and using the Template Toolkit, you can make the form that is generated conditional upon whatever you want using normal Perl conditionals, loops, etc.
Another technique I often use is to nest templates, using an include directive, so you could have a sub-template for a single-field form input section, and a different sub-template for a section that requires two fields, etc.
Hope this helps!
The way forward always starts with a minimal test.
| [reply] |
not 100% sure what "how should I structure the form description if I have to account for various deviations from the template" means
Let's look at the one-field input vs two-field input example. If all my inputs were one-field, I could describe the form in a relatively simple data structure:
[
{
id => 'alpha', numeric => [ 0, 1 ],
name => "Flux capacitance", units => ..., doc => ...,
# absence of default means it's required
},
{
id => 'beta', numeric => [ -100, +100 ],
name => "Bogon density", default => 0, units => ...,
doc => ...,
},
]
and have a both relatively simple loop in a template to generate a form and a straightforward way to validate once it's submitted. But a two-field input uncovers a conceptual difference between the form and the backend: in a form (for a user), both fields compose a single semantic unit ("Wavelength range"); for the validator, fields from a request are separate entities to be considered individually. If I prepare this description from the user's point of view ([ { name => "Wavelength range", ids => [ 'lowwl', 'upwl' ], ... }, ]), I make it harder for the validator to work ("now, where in one of those arrays can I find the parameter name I've just received?") and, arguably, I'm putting markup in the code, which is discouraged nowadays (what's next, putting CSS classes in the same form structure?). If I continue to work with this description from the point of view of the validator (as above), I cannot store the layout information for the generator to produce multiple fields per one conceptual "input".
Is what I'm experiencing Inner-Platform Effect in action? Should I give up and keep it simple, at the cost of some duplication? | [reply] [d/l] [select] |
<label for="[% fields.0.id %]">[% fields.0.name %] = </label>
<input
name="[% fields.0.id %]" placeholder="[% fields.0.placeholder
+%]" type="text"
id="[% fields.0.id %]" [% fields.0.required %] pattern="[% fie
+lds.0.regex %]"
>
[% FOREACH field IN fields %]
[% NEXT IF loop.first %]
<label> - </label>
<input
name="[% field.id %]" placeholder="[% field.placeholder %]" ty
+pe="text"
id="[% field.id %]" [% field.required %] pattern="[% field.reg
+ex %]"
>
<label for="[% field.id %]">[% field.units %]</label>
[% END %]
$fields = [{
id => 'loww1',
placeholder => 400,
...
}, {
...
}];
Hope this helps!
The way forward always starts with a minimal test.
| [reply] [d/l] [select] |