Related issue  DEV-883 - Getting issue details... STATUS


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  MGNLUI-2542 - Getting issue details... STATUS ).  

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


PoC https://git.magnolia-cms.com/users/jsimak/repos/ui/pull-requests/1/commits/91980c4dbd0803b429e50f037f4b70a638cce0dd

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



Form configuration example (YAML)
# 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
        phone: # a form component can have sub-form components with their own layout
          class: info.magnolia.ui.form.poc.definition.FormDefinition
          fields:
            areaCode:
              class: info.magnolia.ui.form.poc.definition.TextFieldDefinition
			number:
              class: info.magnolia.ui.form.poc.definition.TextFieldDefinition
          layout:
            class: info.magnolia.ui.form.poc.layout.MyLayout	
      layout: # this is the layout for address
        class: info.magnolia.ui.form.poc.layout.DefaultLayoutDefinition
  # 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 plain Java class

  • it stores
    • 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

  • is currently the orchestrator of the form building process
  • creates the final form view (a Vaadin Layout)
    • instantiates a Form from a FormDefinition
    • returns the form view to the caller



Snippet from FormPresenter
...
 /***
 * @return a {@link View} as described by this {@link FormDefinition} including nested forms, if any.
 */
public View createFormView(D formDefinition) {
    FormView view = new FormView();
    Form<T> form = formFactory.createForm(formDefinition);
    view.addComponent(form.getLayout());

    return view;
}

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)




  • No labels

2 Comments

  1. Thanks for the status update. I've some questions still:

    1) FormViewFactory#createFormView  - why do we have to wrap the form layout into yet another CssLayout?

    2) What is the point of the Form component itself? All the functionality/data it does/holds seems to be redundant: the property ↔ component mappings should be retrievable from the binder, validation - ditto. The meaning of attachToLayout method is also still not clear to me. Could we go without introducing a custom component? 


    1. 1) Agreed, it's useless.

      2) I'll see if I can do without it. So far I've found helpful thinking of Form as a component, like any other Vaadin field, plus a layout def, which is basically what differentiates it from a plain field. I agree though that there's no need to keep mappings there when you can get them from the Binder. attachToLayout should eventually disappear or be just an internal method that it's called recursively when the form component is eventually built and attached to a View.