In this post I will try to go through high-level design aspects of domainql-form. How I initially envisioned them to work and how they evolved over time driven by our experiences with it.
The first concept was really simple. We have a MobX domain object that represents the root object for the part of the domain we manage with the form, and then we have the <Form/> component that receives the object as value prop.
The <Field/> components reference paths within the object graph by lodash like paths. Here for our example “owner.name”, which first references the owner object that is embedded as property within the root object and then the name property within that owner object.
I mostly imagined one big <Form/> component per view.
This works and for large parts still works this way, but over time we slowly evolved into a more complex model.
The first thing that became on issue was the of course well-known fact that you can’t nest forms within HTML. And at first it seems like, duh, who would do something like that? But then you get a use-case where you have some form fields and then data-table connected to that object and then some more form fields. And of course, the <Datagrid/> needs to do all kinds of form control related things like choosing pagination sizes, letting the user enter filter values etc pp.
Problem: Awkard forms
While in some cases it is just natural to have sub-ordinate objects connected in the form and to edit fields within those, but as soon as you get to lists or deeply nested property paths it gets awkward very quickly, especially if you have to construct the paths dynamically.
Solution: Many Forms Paradigm
So we obviously need to be able to have many forms, often referencing the same object. But we also have cases where we want to edit the nth element out of a list of associated entities.
The form components now point anywhere they like. One root object, many root objects, objects within root objects, doesn’t matter.
We just have many forms that write into the same (non-isolated) objects. These <Form/> components all have their own <form/> elements. But what we want most of the time is that the forms behave as if they were one form.
If the user has entered erroneous data and there is an error displayed, all non-discarding buttons must be disabled. Only things like “Cancel” can remain enabled.
This orchestration of <Form/> component functionality is handled by the new FormContext class. There is a default context that is always used unless the application author created and referenced another FormContext. e.g. For processes that have to independent form-flows side-by-side or a main process and a sidebar-process.
The FormContext also registers all available memoized field-contexts which can be used to implement high-level form behavior on top of domainql-form.