Start tracking server errors.
We've been storing client-side errors for a while now, so we can say "hey that
input you entered is really screwed up, please don't even bother submitting the
form." But once we pass that hurdle, it's still possible that the server will
say "I don't care what the client-side validation said, this input is still
wrong."
To achieve this, we needed to start tracking server errors and client errors
_separately_. The plan, originally, was just to put them in a single array, all
mixed together--and, of course, that's what our ValidationError component
requires--but there's a usability problem with this. The server error could be
"you don't have an internet connection" (using 'server-error' loosely...) or
"hey, the server had a problem handling your request, try again." The problem
with putting everything together is that all these "try again" errors should
get displayed to the user, but anything displayed to the user causes the
Register button to be disabled. Meaning they can't try again until they
(unintuitively) change pretty much any field in the form.
The answer was to separate the two types of errors into their own arrays, and
disable the button based on the client-side error array. When displaying errors,
we concatenate the arrays. This means server-side errors only get displayed, but
do not disable the button.
Of course, we can have server errors that can mean "your input is bad and you
should feel bad", which are decidedly different from "try again later" errors.
These "your input is bad" errors hypothetically _should_ keep the form from
being submitted until the value is changed to a valid input. Unfortunately, if
we get to the point where a server error is returned, we've already proven the
client is incapable of determining between invalid and valid inputs for that
specific condition. If the client were capable of knowing, the input never would
have reached the server in the first place. So considering the point of
disabling the form input is to prevent unnecessary requests to the server, we
should never disable the form based on a server error, because (by definition)
those requests _must_ be necessary, or the client-side validation would have
caught them.
TL;DR: disabling a form because of server-side errors is silly.
We also fixed a bug in the ValidationError component that would cause any error
without a field, param, or header value set (e.g., an error that only contains
the error field) would be considered a match for _every_ input, which is not
what we want.
We still need to decide on logic for displaying global errors. On the naive
level, it's easy: match '/' or '' as the field. But that ignores any error
responses that we are not specifically looking for. I'd much rather have a
catch-all ValidationError handler that will catch any error not already handled
by the other ValidationError instances. Unfortunately, I can't wrap my head
around a good way to denote that a ValidationError instance has handled a
specific error. The best I've got is to do an InverseValidationError that tracks
a list of fields, params, and headers (the inputs available for ValidationError)
and handles any error that _doesn't_ match them. Unfortunately, for complex
forms, this would involve quite a bit of manual bookkeeping, updating
ValidationError and InverseValidationError components in sync and never
forgetting to update one or the other.
1 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2 <!-- Created with Inkscape (http://www.inkscape.org/) -->
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 xmlns:xlink="http://www.w3.org/1999/xlink"
11 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
17 inkscape:version="0.48.2 r9819"
18 inkscape:export-filename="/Users/paddyforan/Desktop/ducky.png"
19 inkscape:export-xdpi="180"
20 inkscape:export-ydpi="180"
21 sodipodi:docname="ducky.svg">
27 inkscape:pageopacity="0.0"
28 inkscape:pageshadow="2"
31 inkscape:cy="60.147186"
32 inkscape:document-units="px"
33 inkscape:current-layer="layer1"
39 fit-margin-bottom="20"
40 inkscape:window-width="1294"
41 inkscape:window-height="835"
42 inkscape:window-x="839"
43 inkscape:window-y="141"
44 inkscape:window-maximized="0"
45 inkscape:object-paths="true" />
49 inkscape:collect="always"
50 id="linearGradient4376">
52 style="stop-color:#000000;stop-opacity:1;"
56 style="stop-color:#000000;stop-opacity:0;"
61 id="linearGradient4348">
63 style="stop-color:#ffffff;stop-opacity:0.720339;"
69 style="stop-color:#ffffff;stop-opacity:0.18644068;" />
71 style="stop-color:#ffffff;stop-opacity:0;"
76 id="linearGradient4336">
78 style="stop-color:#f9f9f9;stop-opacity:1;"
82 style="stop-color:#f9f9f9;stop-opacity:0;"
87 id="linearGradient4048">
89 style="stop-color:#e0c700;stop-opacity:1;"
95 style="stop-color:#ebd100;stop-opacity:1;" />
97 style="stop-color:#fce100;stop-opacity:1;"
101 style="stop-color:#ffe300;stop-opacity:1;"
106 id="linearGradient3845">
108 style="stop-color:#e3e000;stop-opacity:0.58119661;"
114 style="stop-color:#ffff3b;stop-opacity:0.52991456;" />
116 style="stop-color:#ffff65;stop-opacity:1;"
120 style="stop-color:#ffff65;stop-opacity:1;"
125 inkscape:collect="always"
126 xlink:href="#linearGradient3845-4"
127 id="linearGradient3851-7"
132 gradientUnits="userSpaceOnUse" />
134 id="linearGradient3845-4">
136 style="stop-color:#e3e000;stop-opacity:0.58119661;"
142 style="stop-color:#ffff3b;stop-opacity:0.52991456;" />
144 style="stop-color:#ffff65;stop-opacity:1;"
148 style="stop-color:#ffff65;stop-opacity:1;"
153 inkscape:collect="always"
154 xlink:href="#linearGradient4048-7"
155 id="linearGradient4054-2"
160 gradientUnits="userSpaceOnUse" />
162 id="linearGradient4048-7">
164 style="stop-color:#9e8d00;stop-opacity:1;"
170 style="stop-color:#d9c100;stop-opacity:1;" />
172 style="stop-color:#fce100;stop-opacity:1;"
176 style="stop-color:#ffe300;stop-opacity:1;"
181 inkscape:collect="always"
182 xlink:href="#linearGradient4048-2"
183 id="linearGradient4054-26"
188 gradientUnits="userSpaceOnUse" />
190 id="linearGradient4048-2">
192 style="stop-color:#cfb800;stop-opacity:1;"
198 style="stop-color:#d9c100;stop-opacity:1;" />
200 style="stop-color:#fce100;stop-opacity:1;"
204 style="stop-color:#ffe300;stop-opacity:1;"
209 inkscape:collect="always"
210 xlink:href="#linearGradient4048-6"
211 id="linearGradient4054-0"
216 gradientUnits="userSpaceOnUse" />
218 id="linearGradient4048-6">
220 style="stop-color:#e0c700;stop-opacity:1;"
226 style="stop-color:#ebd100;stop-opacity:1;" />
228 style="stop-color:#fce100;stop-opacity:1;"
232 style="stop-color:#ffe300;stop-opacity:1;"
237 inkscape:collect="always"
238 xlink:href="#linearGradient4048"
239 id="linearGradient4184"
240 gradientUnits="userSpaceOnUse"
246 inkscape:collect="always"
247 xlink:href="#linearGradient4048-6"
248 id="linearGradient4186"
249 gradientUnits="userSpaceOnUse"
250 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
256 clipPathUnits="userSpaceOnUse"
260 style="fill:#008000;fill-opacity:1"
266 sodipodi:r2="94.23571"
268 sodipodi:arg2="0.39269908"
269 inkscape:flatsided="true"
271 inkscape:randomized="0"
272 d="M 221,143 191.12489,215.12489 119,245 46.875108,215.12489 17,143 46.875108,70.875108 119,41 191.12489,70.875108 z"
273 transform="translate(-111.43993,46.15993)" />
276 clipPathUnits="userSpaceOnUse"
280 style="fill:#008000;fill-opacity:1"
286 sodipodi:r2="94.23571"
288 sodipodi:arg2="0.39269908"
289 inkscape:flatsided="true"
291 inkscape:randomized="0"
292 d="M 221,143 191.12489,215.12489 119,245 46.875108,215.12489 17,143 46.875108,70.875108 119,41 191.12489,70.875108 z"
293 transform="translate(-111.43993,46.15993)" />
296 inkscape:collect="always"
297 xlink:href="#linearGradient4348"
298 id="linearGradient4354"
303 gradientUnits="userSpaceOnUse" />
305 inkscape:collect="always"
306 xlink:href="#linearGradient4376"
307 id="linearGradient4382"
312 gradientUnits="userSpaceOnUse" />
314 inkscape:collect="always"
315 xlink:href="#linearGradient4048-60"
316 id="linearGradient4184-8"
317 gradientUnits="userSpaceOnUse"
323 id="linearGradient4048-60">
325 style="stop-color:#e0c700;stop-opacity:1;"
331 style="stop-color:#ebd100;stop-opacity:1;" />
333 style="stop-color:#fce100;stop-opacity:1;"
337 style="stop-color:#ffe300;stop-opacity:1;"
342 inkscape:collect="always"
343 xlink:href="#linearGradient4048-6-4"
344 id="linearGradient4186-9"
345 gradientUnits="userSpaceOnUse"
346 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
352 id="linearGradient4048-6-4">
354 style="stop-color:#e0c700;stop-opacity:1;"
360 style="stop-color:#ebd100;stop-opacity:1;" />
362 style="stop-color:#fce100;stop-opacity:1;"
366 style="stop-color:#ffe300;stop-opacity:1;"
368 id="stop4052-93-1" />
375 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
376 gradientUnits="userSpaceOnUse"
377 id="linearGradient5318"
378 xlink:href="#linearGradient4048-6-4"
379 inkscape:collect="always" />
381 id="linearGradient4048-4">
383 style="stop-color:#e0c700;stop-opacity:1;"
389 style="stop-color:#ebd100;stop-opacity:1;" />
391 style="stop-color:#fce100;stop-opacity:1;"
395 style="stop-color:#ffe300;stop-opacity:1;"
400 inkscape:collect="always"
401 xlink:href="#linearGradient4048-6-46"
402 id="linearGradient4186-2"
403 gradientUnits="userSpaceOnUse"
404 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
410 id="linearGradient4048-6-46">
412 style="stop-color:#e0c700;stop-opacity:1;"
418 style="stop-color:#ebd100;stop-opacity:1;" />
420 style="stop-color:#fce100;stop-opacity:1;"
424 style="stop-color:#ffe300;stop-opacity:1;"
426 id="stop4052-93-3" />
429 inkscape:collect="always"
430 xlink:href="#linearGradient4048-5"
431 id="linearGradient4184-7"
432 gradientUnits="userSpaceOnUse"
438 id="linearGradient4048-5">
440 style="stop-color:#e0c700;stop-opacity:1;"
446 style="stop-color:#ebd100;stop-opacity:1;" />
448 style="stop-color:#fce100;stop-opacity:1;"
452 style="stop-color:#ffe300;stop-opacity:1;"
457 inkscape:collect="always"
458 xlink:href="#linearGradient4048-6-3"
459 id="linearGradient4186-4"
460 gradientUnits="userSpaceOnUse"
461 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
467 id="linearGradient4048-6-3">
469 style="stop-color:#e0c700;stop-opacity:1;"
475 style="stop-color:#ebd100;stop-opacity:1;" />
477 style="stop-color:#fce100;stop-opacity:1;"
481 style="stop-color:#ffe300;stop-opacity:1;"
483 id="stop4052-93-0" />
490 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
491 gradientUnits="userSpaceOnUse"
492 id="linearGradient7857"
493 xlink:href="#linearGradient4048-6-3"
494 inkscape:collect="always" />
496 inkscape:collect="always"
497 xlink:href="#linearGradient4048"
498 id="linearGradient8538"
499 gradientUnits="userSpaceOnUse"
505 inkscape:collect="always"
506 xlink:href="#linearGradient4048-6"
507 id="linearGradient8540"
508 gradientUnits="userSpaceOnUse"
509 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
515 inkscape:collect="always"
516 xlink:href="#linearGradient4048"
517 id="linearGradient8550"
518 gradientUnits="userSpaceOnUse"
524 inkscape:collect="always"
525 xlink:href="#linearGradient4048-6"
526 id="linearGradient8552"
527 gradientUnits="userSpaceOnUse"
528 gradientTransform="matrix(-1,0,0,1,410,652.36218)"
534 inkscape:collect="always"
535 xlink:href="#linearGradient4048-6"
536 id="linearGradient8555"
537 gradientUnits="userSpaceOnUse"
538 gradientTransform="matrix(-1,0,0,1,410,636.36218)"
544 inkscape:collect="always"
545 xlink:href="#linearGradient4048"
546 id="linearGradient8558"
547 gradientUnits="userSpaceOnUse"
552 gradientTransform="translate(0,636.36218)" />
554 inkscape:collect="always"
555 xlink:href="#linearGradient4048-25"
556 id="linearGradient8558-8"
557 gradientUnits="userSpaceOnUse"
562 gradientTransform="translate(0,636.36218)" />
564 id="linearGradient4048-25">
566 style="stop-color:#e0c700;stop-opacity:1;"
572 style="stop-color:#ebd100;stop-opacity:1;" />
574 style="stop-color:#fce100;stop-opacity:1;"
578 style="stop-color:#ffe300;stop-opacity:1;"
583 inkscape:collect="always"
584 xlink:href="#linearGradient4048-6-8"
585 id="linearGradient8555-6"
586 gradientUnits="userSpaceOnUse"
587 gradientTransform="matrix(-0.99999999,0,0,1,410,626.36218)"
593 id="linearGradient4048-6-8">
595 style="stop-color:#e0c700;stop-opacity:1;"
601 style="stop-color:#ebd100;stop-opacity:1;" />
603 style="stop-color:#fce100;stop-opacity:1;"
607 style="stop-color:#ffe300;stop-opacity:1;"
609 id="stop4052-93-6" />
616 gradientTransform="translate(-6.04e-6,626.36218)"
617 gradientUnits="userSpaceOnUse"
618 id="linearGradient8584"
619 xlink:href="#linearGradient4048-25"
620 inkscape:collect="always" />
627 <dc:format>image/svg+xml</dc:format>
629 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
630 <dc:title></dc:title>
635 inkscape:label="Layer 1"
636 inkscape:groupmode="layer"
638 transform="translate(-59.999997,-686.36218)">
643 d="m 205,706.36218 0,250 51.78125,0 L 330,883.14343 l 0,-103.5625 -73.21875,-73.21875 -51.78125,0 z"
644 style="fill:url(#linearGradient8558);fill-opacity:1"
645 inkscape:connector-curvature="0" />
648 d="m 205,706.36218 0,250 -51.78125,0 L 80,883.14343 l 0,-103.5625 73.21875,-73.21875 51.78125,0 z"
649 style="fill:url(#linearGradient8555);fill-opacity:1"
650 inkscape:connector-curvature="0" />
653 inkscape:connector-curvature="0"
655 d="m 257,808.4383 0,53.84777 -17.57359,38.07611 -24.85282,0 L 197,862.28606 l 0,-53.84776 17.57359,-38.07612 24.85282,0 z"
656 style="fill:#f9f9f9;fill-opacity:1" />
658 inkscape:connector-curvature="0"
660 d="m 214.99999,808.4383 0,53.84777 -17.57359,38.07611 -24.85281,0 -17.5736,-38.07612 0,-53.84776 17.57359,-38.07612 24.85282,0 z"
661 style="fill:#f9f9f9;fill-opacity:1" />
663 inkscape:connector-curvature="0"
665 d="m 199,830.42105 0,19.88225 -6.44365,14.05888 -9.1127,0 L 177,850.3033 l 0,-19.88225 6.44365,-14.05887 9.1127,0 z"
666 style="fill:#000000;fill-opacity:1" />
668 inkscape:connector-curvature="0"
670 d="m 237,830.42105 0,19.88225 -6.44365,14.05888 -9.1127,0 L 215,850.3033 l 0,-19.88225 6.44365,-14.05887 9.1127,0 z"
671 style="fill:#000000;fill-opacity:1" />
673 style="fill:#ff8b00;fill-opacity:1;stroke:none"
674 d="m 302.02397,911.28458 -45.24272,45.0776 -103.38652,0 -44.35762,-45.0776 44.35762,-44.85924 103.21095,0 z"
676 inkscape:connector-curvature="0"
677 sodipodi:nodetypes="ccccccc" />