crizmas status update and the form interlaid asynchronous validations

Raul-Sebastian Mihăilă
7 min readJul 29, 2018

Edit 2021–01–03: There is a newer version of this article, adapted to use v2 of crizmas-mvc.

Important updates were published for crizmas-mvc on the 21st of April 2018. We’re writing this article so late because we’ve been extremely busy for the last few months. Beside small refactoring and small additions to the form API, the most important updates are the great improvements implemented for the asynchronous validation API provided by the crizmas-form package, and complete suites of automated tests for 5 of the crizmas related packages (including the core and the form packages). The biggest todo on our list is writing a complete suite of tests for the router package.

In this article we continue presenting our form asynchronous validation approach.

First, a small side note. It is often considered that a form gathers temporary data, and only after that data is submitted, it can become persistent. This isn’t always the case. It’s possible that a domain object is in an erroneous state and you may want to reflect that in a form used for gathering values for different parts of that object. Since crizmas-form allows you to associate models with forms and inputs, the validation process allows reflecting the validation state of the model in the form as well.

A form framework must provide means to validate the inputs, by allowing you to specify validation functions that are called when certain events occur. This means that the validation process has two sides: 1) the form framework has a certain logic of calling the validation functions and 2) the validation functions themselves have a certain logic of deciding whether the input has a valid state or not.

An important feature that a form framework should provide is async validation, which can be tricky as we’ll see. As opposed to the async validation, the sync validation is very simple conceptually and intuitive. An important aspect is that crizmas-form allows triggering the validation when any event happens. This means that the validation process can have an event associated. There are certain standard events for which crizmas-form triggers the validation, but it can be triggered by the user as well, with any custom event. Typically an event also has a target associated. For instance, since crizmas-form allows you to build trees of inputs, we might want to validate all the inputs when an input’s value is changed. That input will be the target of the change event.

Being able to validate another input when a certain input’s value changes is important. For instance, if we have a start date and an end date, and the end date must be greater than the start date, we can use the target of the event to display the error in the right place, which allows for better user experience. For instance, if I’m setting the dates properly and then later I change my mind and update the end date to be earlier than the start date, normally the end date is the one that is correct, because I probably had a reason to make the changes by starting with the end date. Therefore, in this case, the start date is incorrect and the error should be displayed under the start date, not under the end date, even though the end date is the input that the user interacted with most recently.

In order to validate inputs that are different from the event’s target we must validate the entire tree of inputs, which is what happens most of the times as specified in the crizmas documentation. When validating an input, crizmas-form first validates its children. This allows the current input to get access to the children errors during its own validation.

The parent input will contain not only its own errors, but also its children errors. After validating the children, their errors are collected into an array. After the parent’s validation function is called, its errors are added to that array and after that the errors array is set on the parent input.

However, when we think of async validations things start to get complicated. The form framework must support common and custom use cases with clear solutions that result in sensible behavior and trade-offs may be needed.

With async validations, it’s possible to have interlaid async validations with other either sync or async validations. These interlaid validations must be managed so that they don’t lead to inconsistencies or surprising race conditions. Different approaches are possible. For instance, we may want an event that triggers async validations to be treated atomically. But what happens if a new event occurs, perhaps even with new form state, while the initial async validation is in progress? Should the initial validation be suppressed by the new validation? Should the new validation be ignored if there is another validation in progress? Or perhaps we want to allow both validations and keep only the result from the one that finishes first. Or the one that finishes last.

Many different scenarios are possible and in each case one or another approach can be more appropriate. With our approach the input initially clears the errors. After that, as mentioned, the parent gathers the children errors after they are validated. This means that if, during the validation process, the children are validated synchronously and the parent asynchronously, the children errors are gathered before the parent’s validation function is called and if a second validation is initiated during the parent’s initial async validation, at the end of the parent’s first validation, the children errors that were gathered may be stale, in case the children have new errors from the second validation. Or perhaps one of the children is validated asynchronously initially and a second sync validation is performed, which means that, during the initial validation, the parent will gather some of the children errors resulted from the second sync validation. What if the children errors were gathered after the parent’s validation has finalized? The issue in this case is that at the end of the validation process that has an event associated with it, the parent will reflect children errors that were caused by another validation with possibly a different associated event, which may lead to logical inconsistencies. At the same time, it wouldn’t make sense to disallow interlaid validations, because it would be against the natural flow determined by the user’s actions together with the changing state of the world. All these approaches have downsides, so how do we solve this?

The key is separation of concerns. As mentioned before: 1) the form framework has a certain logic of calling the validation functions and 2) the validation functions themselves have a certain logic of deciding whether the input has a valid state or not. This helps us taking the simplest route: the form framework does whatever is easier to understand and the validation functions can have any custom logic and can be as complex as needed in order to reflect whatever behavior is desired. We considered that gathering the children errors right before the parent validation function is called is the most intuitive approach as it appears to be an atomic process from the parent’s perspective and at the same time is agnostic and permissive, meaning that interlaid validations are allowed and the parent doesn’t care in which validation process the children errors were set. Whatever extra logic is needed to obtain the desired behavior must be contained in the validation functions.

This means that in complicated situations the validation functions must be aware of possible interlaid validations and must be explicit in handling the possible race conditions in order to clearly express the desired behavior. This can be complicated, but crizmas-form saves the day by providing a useful API for implementing interlaid async validations for practical cases.

Let’s look at three cases and see how the crizmas-form simple API can be used. In the first two cases we have a form with one or multiple inputs with async validation logic. In these cases whenever we update such an input we want to trigger the async validation again which results in making a call to the server to validate the input value, without waiting for the previously initiated validation that is in progress (interlaid async validations) and is ignored. We also don’t care about the order in which the responses come from the server. In other words, these are cases where the later validations suppress the previous validations. In the third case we will look at a different flow and we will see how the agnostic nature of the form framework can be useful.

The first case: a username field with async validation and a password with sync validation.

The second case: a username and a password fields, both with async validation.

The same case 2, but let’s see what happens if a first (username) async validation finishes after a second (password) async validation.

This works because, even though the password field is validated synchronously the first time, its async validation finishes before the username’s asynchronous initial validation and so when the form gathers the children errors it gathers the password error resulted from the already finished async validation.

The third case: make an initial call to the server to get the maximum allowed number of rows in a form presented as a table, during which any other validations are allowed and any gathered error must be reported at the end. The key here is for the root to make the initial validation that every other input ignores. At the end of the validation, the root reports its current errors already gathered from other validations together with the potential new error. As new rows are added or removed from the form, we revalidate the form based on the maximum count of rows we fetched from the server.

Of course, another approach would be to simply fetch the maximum rows count (outside the validation function) and after that trigger a validation on the form and report the error, if there is one. However, the presented approach allows separating the form definition and controller logic completely from the validation logic. The validation function shouldn’t trigger the validation itself, it should just validate (perform the validation logic). Another aspect is that with the presented approach the submission is blocked while an async validation is in progress (in case you forget to disable the submit button) (as long as the form is under an observed root).

Hopefully this shows how easy it is to use crizmas-form and also how helpful it is in managing complex scenarios.

--

--