Wednesday, April 26, 2017

Forms as state containers Part 1: forms as a single source of truth

Please read the introduction if you haven't already.

Everything is a form

HTML forms are an age-old standard, and by no means replaceable. I've created a form builder on Dojo Toolkit some years ago, and for all its javascript fanciness, it was driven by the idea that forms can and should be the only means for users to interact with the web. Want to create a fancy slider, a large editable grid, or a even simple (yet interactive) list? Be sure to wrap it in a form element! Why? Because form components manage their own state internally. As soon as your component exposes anything else then a simple "value" property to allow interaction with javascript, you're going to leak state into parts of the application that don't have anything to do with it, making things literally hell to maintain.

Dojo Toolkit was the first large javascript frameworks for building widgets AKA web components (known as Dijits), and had some flaws in the state department, mainly because widgets always inherit from other widgets as a way of isolating application logic. In many, if not all, cases this meant that handing down state in the object hierarchy is inevitable. Newer frameworks learned from Dojo and put internal widget state in explicit containers, which are, let's say, marked for danger. Now, this is as good as it gets. Some developers will argue that component X will eventually need information from component Y, so you should maintain state centrally, outside of components. Enter web forms. And more importantly, web standards. Allow me to explain.

Data isolation

Each component in a form should maintain its own state, no matter how complex. The only thing a form component should expose is its value, which can be retrieved via its name. As you may know, you can extract all values in a form by iterating over all its components. Typically we can represent this data as a key/value map, where each component name is mapped to its value. We don't collect all form data all the time, we only do so when we need to process it. I see three possible moments for this to happen. The first is, obviously, upon submission. The second is when you want to store data offline, so the user won't loose it when something unexpected happens. When should data be stored offline? That depends how valuable the contents it. It's possible to store on every keystroke, but also after several, or according to a schedule.

The third moment the value of a form needs to be extracted is more interesting, but also much more rare. It's when an application is used in a secure environment, and each modification should be saved across the wire, like a live connection. This can happen for instance in banking or in multi-user apps, like a collaborative platform. I do have a solution for this, but I won't go into it now, as it really is a lot more complex. I'll divulge the solution in return for some cold hard cash...

Events bubble up

Because in forms, data is so perfectly isolated, it's also a challenge when you do indeed need information across interdependent components. I should probably give an example here to make it more concrete. Let's say I got some nice date picker widget somewhere off the web. I would like to be able to select two dates, and when both are selected, I want to automatically enter the offset between the two dates (say, in hours) in a number box below the date pickers:







OK, maybe not so nice widget, but hey, I got it like this out of the box for free. So, when both the start and end dates (yeah, you only see times) are set, the hours box is filled. I could register some event listeners on both date pickers that fire two handler functions when a date is selected, and in both handlers check for the other date to be set. However, that means I can't make it more generic, for instance when I would have multiple instances of this form, like in a calendar app. I can't really create a single widget to do all this internally, because it would violate returning a single "value" from it, as I actually have three values. How to do it then?

Instead of managing events locally, I just register event listeners at the top, which means on the form. Isolation of data doesn't mean I need to isolate events as well, on the contrary. Also, when registering events at the top, I have a central mechanism for managing client logic from the user's side of things. We could separate events by type, most notably "change", "click", "update", "focus", "blur", "submit", and match the event target using a regular CSS selector, as available since the arrival of Element.matches().

Having a single handler for all form events looks a bit like a controller you'd have in the over-popular MVC pattern, or the over-hyped Rx (reactive) way of doing things, all straight out of the box, and more importantly, standards-based. Throw application routing in the mix using some very basic pushState script, and you can start building applications without worrying to much about state, as you got perfect isolation and central management.

Next up: Forms as state containers Part 2: managing complex data

No comments:

Post a Comment