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.
...
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 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: class: 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: 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
- its 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 passes 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, mappings)); view.addComponent(rootLayoutview.addComponent(form.getLayout()); 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)