learnings, nerdisms, bicycles
AmpersandJS forms. Amazing little constructs. But to make them render
reliably
and consistently--it was a battle! One particular bit, indicating their
rendered
state, proved to be much more complicated than anticipated.
Let's talk context. @MRN, we do a lot of assessessments. What's an assessment. An assessment is a glorified term for research survey, in nuero-speak. What do surveys need? Forms. @Henrik Joreteg's blurb on web forms is one of the most refreshing short bits declaring how forms should really behave. When I first read it, I was very curious to see if the hype was true. Consider my prior strategies managing form data:
// does this look familiar?
var form = service.getMyOldFormValues();
var $input = jQuery("#sillyInput");
if (form.input.init !== undefined) $input.val(form.input.init);
// inside some change handler...
if ($input.val() === "bananas") {
console.warn(
"how many huge, redundant condition blocks like me will a complicated form have?",
);
}
// what's great about this? i get my data in one place, i initialize my control in another, i validate in a third, and hopefully i've got some standard notification mechanism for validation messages and submissions. abort!
<form>
constructs to
send data, vs. opening my eyes and seeing what a {field: value, ...}
form
data-summary could really do. Validation? Still not great. A few nice
directives make it somewhat easier. Let's not dig there though. :)ampersand takes a modern approach:
This is a drastic improvement over our existing form handling strategies. Why.
Because we've decoupled ourselves from the formal <form>
behaviors, and used
the form for it's original purpose--get data from the user, not to
get-the-data-that-may-or-may-not-be-in-the-format-we-need and to immediately
submit said data to some remote page. <form>
has lots of sugar to make forms
work in a fashion that were helpful when we did all of our web application work
on the server. That time is gone.
The API's for many of these &-js form-fields were slightly inconsistent. The
docs? They all had a good start, but were still lacked key details. For example,
between a couple of FieldViews, you may not be sure the fate of an el
that you
provided to the field. Would the FieldView
el
its control (e.g. if the provided el
was an input),It was challenging. There were more than a couple bugs. In the beginning, I just needed to get my app to work, so I started tossing in patches n' bugfixes. More than just bugfixes, however, we wanted a consistent interface to enable the form itself to perform operations on its member fields. This has been mostly completed via form-view initiatives.
So what does this have to do with the managing a single bit? Again, @MRN, users
consuming our product, COINS, fill out a variety of
complex forms, often with custom in-house designed controls (cool!). Form
management is our business. Trouble is, the system is huge and it's all
written using a flat, procedural, global-esqe data paradigm. Surveys are built
server-side with logic, DOM templates, and data embedded into {js-objects}
which in turn encoded into the resultant HTML fetched from the server. It's made
up-keep a bit tricky.
Ok, let's replace the form templates from the server with more flexible
ampersand-form-view
s, and simply pipe in data to the form for it to manage.
Unfortunately, the mapping isn't so clean. A survey is really a collection of
forms, paginated. To address this very problem, I built and released
ampersand-form-manager-view.
It's a handy little library to take a series of forms, and enable you to get the
state of the set of forms. That is, provide it a series of forms, and it could
tell you that all forms are complete and valid, or not! This is where that
single bit came to bite!
The single bit under fire was the rendered
boolean flag in &-view.
&-form-manager-view cycles through forms. However, when cycling back to a
previously viewed form... nothing would show!
This leads us to rendered definition 1: the view is rendered
if it has an
element. This was the state of &-view when I began. How dishonest! ;) The fault
of this definition is that my views had been cycled in and out, hence removed.
However, the element they were rendered into still existed! Therefore, the
view still believed it was rendered! You might say "just remove the el
from
the view defintion" on view removal. This doesn't work because often a view will
be initialized with an el
, but not yet rendered on screen. In that case,
rendered
would report true
, when indeed, all we did was initialize, not call
the render()
function itself.
definition 2 is that the view is never rendered
. Remove rendered
state
from the &-view library. The rendered state of a view shouldn't be managed by
&-view. Instead, let the user manage this piece of state on his/her own. This
drastically cuts down on usability, and bloats &-view defintions.
definition 3 is that the rendered
bit gets autoloaded into the state as a
prop
, and render()
and remove()
functions toggle the bit. When the user
overrides one of these functions, a custom getter/setter wraps the user provided
function with logic to set the bit.
Although some implementations of definition 3 trigger redundant events if
the prototype is extend
ed, an agreeable solution was found using session-wise
and derived+cached attributes. The solution sets a session
_rendered
attribute to true
on every call to render()
. There may be many calls to
various render()
fns if a View definition is extended multiple times. This
emits many events, but the view is really only rendering once. So, because we
only want to emit one event for the user to actually act on, we exploit the fact
that the derived rendered
attribute only executes its value handler when the
session value _rendered
has changed. In this regard, only a single
change:rendered
event gets emitted, and we can manually trigger a matching
render
event at the same time, as some folks want to listen for the action vs.
the state.
It was challenging to land on a solution that would honor &-view's contracts for eventing as well as pre-rendered, rendered, and post-rendered state. You can see bits and pieces in the ampersand-state source.
What a rabbit hole. We visited forms, form-management and composition, jumped over to views, then finally examined how I ended up making stateful views honest! There's often a story behind each PR. This was the story behind mine.