Page History
Related issue Jira server Magnolia - Issue tracker columns key,summary,type,created,updated,due,assignee,reporter,priority,status,resolution serverId 500b06a6-e204-3125-b989-2d75b973d05f key DEV-883
Table of Contents
Goal
We want to provide users with a simple, modern API to build arbitrarily complex fields.
Problem
All is good, as long as a simple value is bound to a simple field, e.g. TextField → String.
But what if I need a field manage a complex nested value, i.e. if I have to manage hierarchical data?
Currently Magnolia does provide such ability through custom multi fields (see AbstractCustomMultiField) but they have several shortcomings, both in terms of internal implementation and from a client developer's perspective (see for instance
Jira | ||||||
---|---|---|---|---|---|---|
|
Proposal
Assume the field is simple and atomic
When you need to manage many related values - it is another form within a form
Complex fields are like forms but with striped stripped layout.
...
Layouting
Form (or editor) layout should be separated from the binding mechanism and should be flexible
first , we produce the field/sub-form components and bind them to the binder
then we pass the mapping of e.g. the property names and the resulting components (fields/sub-forms) to a layout producer
the layout producer could yield a tabbed layout that we have currently or any other type of layout based on the configured definition
A class diagram from the current PoC and a description of their main responsibilities
FormDefinition
- is a ComponentDefinition
- is registered and mapped to a factory class like any other form field
- has FieldDefinition(s)
- has a
LayoutDefinition
Code Pro | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
# this is the 'main' form configuration form: fields: title: class: info.magnolia.ui.form.poc.definition.TextFieldDefinition firstName: nameclass: info.magnolia.ui.form.poc.definition.TextFieldDefinition lastName: class: info.magnolia.ui.form.poc.definition.TextFieldDefinition # address is a form component (a subform of the main form) and specifies its own layout. # It is declared under fields and we can refer to it as a field # see below form/layout/tabs/more/fields/address address: class: info.magnolia.ui.form.poc.definition.FormDefinition fields: city: class: info.magnolia.ui.form.poc.definition.TextFieldDefinition country: class: info.magnolia.ui.form.poc.definition.TextFieldDefinition nestedphone: # a form component can have sub-form components with their own layout class: info.magnolia.ui.form.poc.definition.FormDefinition fields: quxareaCode: class: info.magnolia.ui.form.poc.definition.TextFieldDefinition number: layout: class: info.magnolia.ui.form.poc.layoutdefinition.MyLayoutTextFieldDefinition fieldslayout: qux:class: info.magnolia.ui.form.poc.layout.MyLayout layout: # this is the layout for address class: info.magnolia.ui.form.poc.layout.DefaultLayoutDefinition fields: city: country: nested: # this is the main form's layout configuration # layout configurations could probably be omitted altogether and rely on a reasonable default layout: class: info.magnolia.ui.form.poc.layout.TabbedLayoutDefinition tabs: personal: fields: title: name:firstName: lastName: more: fields: # address is a form component (a subform of the main form) # here it is referred to as if it were a plain Field address: |
LayoutDefinition
- specifies
LayoutProducer
implementation class
LayoutProducer
- is instantiated from a LayoutDefinition
- createLayout(LayoutDefinition, Map<String, Component>) creates the actual layout (a Vaadin Component) from
- LayoutDefinition
- a mapping of field names and actual field components
Form
is a component, same as a fieldplain Java class
- during its building process it stores
- a mapping of field names and resulting field components
- it own LayoutDefinition
- exposes mappings + LayoutDefinition via functional interface e.g.
form.attachToLayout((layout, mappings) -> doLayout(layout, mapping))
attachToLayout
internally takes care of creating the main form layout and all subforms' layouts (if any)- its Binder
- its own Layout (including sub-form layouts)
- its sub-forms
- can save and validate self and its sub-forms recursively (should this functionality move away from Form?)
FormPresenter
FormViewFactory
- is currently the orchestrator of the whole form building process
- creates the final form view (a Vaadin Layout)
- instantiates a Form from a FormDefinition
- instantiates a LayoutProducer calls form.attachToLayout(..) and calls a lambda function which eventually delegates to the LayoutProducer
returns the form view to the caller
Code Pro | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
... /*** * @return a {@link View} as described by this {@link FormDefinition} including nested forms, if any. */ public View createFormView(D formDefinition) { CssLayoutFormView view = new CssLayoutFormView(); Form<T> form = formFactory.createForm(formDefinition); Layout rootLayout = form.attachToLayout((layout, mappings) -> createLayout(layout, mappingsview.addComponent(form.getLayout()); view.addComponent(rootLayout); return view; } protected Form<T> createForm(D formDefinition) { //TODO how to instantiate the correct PropertySet? FormFactory<D, T> formFactory = componentProvider.newInstance(FormFactory.class, formDefinition, subAppContext, new JcrNodePropertySet(collectFormFieldDefinitions(formDefinition), null, null), componentFactoryFactory); return (Form<T>) formFactory.createComponent(); } protected Layout createLayout(LayoutDefinition definition, Map<String, Component> mappings) { LayoutProducer producer = componentProvider.newInstance(definition.getImplementationClass()); return producer.createLayout(definition, mappings); }} |
TODO
We now have the basic infrastructure to build and bind form components (including arbitrarily nested, complex sub-forms). Layout is split from field definitions. We can also read and write forms and sub-forms without the need for special transformers.
What is left to do.
- Form
- styling
set labels, help messages and descriptions: use something like info.magnolia.ui.dialog.formdialog.FormView?
update forms/fields based on Locale? See info.magnolia.ui.dialog.formdialog.FormPresenterImpl.updateForm()
pass FormPresenter to DetailPresenter(..) which takes care of initializing actions and other stuff before calling and setting the form view?
- Fields
- implement them or harden the ones which we began to implement (TextField, SelectField)
- populating select options based on the value of another field
- validating a field depending on the value of another field (including within a composite field itself)
- enabling/disabling fields conditionally
- potentially custom handling of any field, via plain Vaadin code
- properly highlighting validation on sub-fields (this should be already taken care of by Vaadin, only need proepr error field styling).
- FormBinder/FormContext
- currently saving a new item fails cause the node id we get is null.
- current implementation relies on JcrNodeItemid and JcrNewNodeItemId: not sure we want to use them
- Layout
- naming
- Custom layout impl doesn't work
- provide a Vaadin declarative layout impl
In case of resource-based layout we could and should provide flt support (i.e. pre-process the script before binding to the fields)