On Sun, 28 Jul 2013 11:11:38 GMT
"Manu Evans" turkeyman@gmail.com wrote:

So... my code ends up looking like:

if(req.args.myArg)
  // do soemthing if myArg is present
else
  // maybe i can default it, or handle missing myArg explicitly

I'm writing a web API, but even in the case of a typical webpage,
consider a sign-up form; some fields are always optional, if you miss
something that isn't optional, some red text "You forgot this!"
appears next to the appropriate missing item for instance... that's
quite context specific. You never get redirected to a webpage that
says "you made an error somewhere in the form, please try again" ;)

You're approaching it in what I call "the ASP/PHP way". I used to do
that, too. But after having done a lot of web forms in various
languages I got really freaking tried of what invariably ended up being
lots of duplicated logic, data structures and error handling for each
form and field (no matter what language I was using). So I've migrated
to an approach somewhat more inspired by desktop GUIs:

First off, I have an internal data structure, HtmlForm, that represents
an HTML form and all its fields. This consists of various information
for each field like displayed label, internal name, type of field
(text, textarea, radio, etc), default value, whether or not it's
optional, whether it's a password field, and whether or not it's a
"confirmation" field intended to match some other field (such as
"retype your password" field) I have one instance of this structure for
each form in the application.

But I don't instantiate/configure that form structure manually (I used
to, but it lead to generated HTML that wasn't easy to customize).
Instead, I have one single component that will read in an HTML page
(actually a Mustache-D HTML template [1]), scan it for forms via Adam
Rupee's HTML DOM [2], and then creates a HtmlForm structure from that.
The HTML template is also checked for form errors and missing
information (such as ensuring all fields have a matching label tag with
appropriate "for" attribute). Some non-standard tags/attributes I
invented for these purposes are stripped from the HTML template, and a
little bit of necessary boilerplate for the templating engine is
inserted (again, via the DOM).

The resulting HtmlForm structure and new slightly-modified HTML
template (still a mustache-d template) are stored for later use. This
process is only performed as-needed (on actual production, it's just
once - upon application startup).

Now come the cool parts:

Whenever a form submission comes in, I pass the entire POST data
directly into the appropriate HtmlForm. It does all the validation
against itself and then returns a FormSubmission. This FormSubmission
contains all the submitted data, plus info about what fields, if any,
were invalid and why (missing required field, confirmation fields
didn't match, etc.). It also contains the error message, if any. At
this point, I can optionally perform any additional form-specific
validation and update the FormSubmission accordingly (but I rarely need
to).

Then, if the FormSubmission says everything validated ok, I'm done. I
can just read out the required fields knowing they exist and are
valid, and go on with whatever I need to.

OTOH, if the FormSubmission says there was a problem, then I stash the
FormSubmission into the session data and redirect the user back to the
form page (to avoid breaking back/reload [3]). The form page grabs the
FormSubmission, passes it back to the HtmlForm which then gives the
Mustache-D templating engine all the necessary data to properly render
the form on my HTML template: Showing the error message, highlighting
the labels for all invalid fields (CSS tweakable, of course), and
repopulating the non-password fields with whatever information the user
did submit.

Final result: Anytime I need a web form, all I need to do is:

  1. Define the form right there in my HTML template.

  2. Pass the entire POSTed data straight into the form engine, stashing
    the result into the session data and checking "isValid": Valid:
    Continue on doing whatever. Invalid: Redirect back to the form.

  3. Anytime I display the page with the form, I pass the data stashed in
    the session (if any) back to the form engine.

  4. All validation, field repopulation, error messages and "bad field"
    highlighting are handled automagically.

It's all rather tightly coupled with the rest of my framework (which
sits on top of vibe.d), and it's very newcomer-unfriendly (nearly zero
docs, zero examples, zero guarantees of API stability, more GC usage
than vibe.d itself, and a lot that could probably be cleaned up), but
FWIW it is all OSS and up on github:

https://github.com/Abscissa/SemiTwistWeb

The form engine in particular is in this file if you wanted to take a
look, but again, there's probably some rough edges and such, and
radio/checkbox/listbox/uploads aren't implemented yet.

https://github.com/Abscissa/SemiTwistWeb/blob/master/src/semitwistWeb/form.d

[1] Mustache-D: https://github.com/repeatedly/mustache-d

[2] Adam's HTML DOM:
https://github.com/adamdruppe/misc-stuff-including-D-programming-language-web-stuff/blob/master/dom.d

[3]
http://blog.httpwatch.com/2007/10/03/60-of-web-users-cant-be-wrong-dont-break-the-back-button/