RFC: Neos FormBuilder / Fusion-based Form rendering

Continuing the discussion from Best capable Form-Builder for Neos:

tl;dr

Two features that are requested quite a lot are: 1. easier manipulation of Form rendering (i.e. custom markup) and 2. an easier way to create Forms within the Neos Backend. Let’s address those.


After the discussion above, I had a fruitful exchange with Peter (from www.internezzo.ch) and Benno (from www.format-d.com) and their agencies kindly offered to sponsor work for a better Form integration to Neos.

Here is how I suggest to approach that:

1. Fusion-based Form Rendering

The configuration and rendering of the Flow Form Framework was always meant to be very flexible and extensible. And it is. But it’s still tedious to adjust the markup of the rendered Form:
You have to copy the affected template/partial/layout files to your package, adjust them and then change the renderingOptions to (also) look for them in the new location.

By using a Fusion View rather than Fluid it would be much easier to adjust the rendering and it could be implemented as separate package, compatible with the existing rendering.

  1. Create a new Package (name tbd, maybe Neos.Form.FusionPreset)
  2. Configure a new preset fusion that can be used/extended for custom forms
  3. Define Fusion prototypes for all default Form element types
  4. Implement a Fusion based Renderer

A first test, using the FusionView already works out quite nicely. It looks a bit like this: https://gist.github.com/bwaidelich/60531d0e55d69bc93f5c7ba598d9e201

In the simplest implementation the Fusion prototypes could be as simple as an Array of Tags like

prototype(Neos.Form:FormElement) < prototype(Neos.Fusion:Array) {
    label = Neos.Fusion:Tag {
        tagName = 'label'
        attributes {
            for = ${element.uniqueIdentifier}
        }
        content = ${element.label}
    }
    field = Neos.Fusion:Tag {
        tagName = 'input'
        attributes {
            id = ${element.uniqueIdentifier}
            placeholder = ${element.properties.placeholder}
        }
        @process.wrap = ${'<p>' + value + '</p>'}
    }
    elementDescription = Neos.Fusion:Tag {
        tagName = 'span'
        attributes {
            class = 'help-block'
        }
        content = ${element.properties.elementDescription}
        @if.condition = ${element.properties.elementDescription}
    }
}

prototype(Neos.Form:MultiLineText) < prototype(Neos.Form:FormElement) {
    field.tagName = 'textarea'
}

Changing the position of the label for all elements (for example) would be as easy as:

prototype(Neos.Form:FormElement) {
 label.@position = 'after field'
}

BTW: This package could also be used in Flow applications (it only has a dependency to neos/form and neos/fusion)

2. FormBuilder in the Neos Backend

There have been some interesting approaches to integrating our existing Form Builder to Neos (see discussion linked above).
But wouldn’t it be nice to be able to use the ever improving Neos Backend UI to create/manipulate form definitions directly.

I’ll create a separate package (for now in my own namespace, later maybe Neos.Form:NeosFormBuilder or similar).
The package will come with an (extensible) list of NodeTypes one for each default Form Element type.
So elements would be reflected as nodes in the ContentRepository, and so would be finishers and validators.

The structure of a Form Definition in the CR could look something like this:

form (Neos.Form:Form)
  pages (ContentCollection)
    page1 (Neos.Form:Page)
      elements (ContentCollection)
        name (Neos.Form:SingleLineText)
          validators (ContentCollection)
        email (Neos.Form:SingleLineText)
          validators (ContentCollection)
            emailValidator (Neos.Flow:EmailAddress)
  finishers (ContentCollection)
    emailFinisher (Neos.Form:Email)

As you can see, the structure is pretty similar to what we have in the persisted forms today already.
However, there are still a couple of challenges to crush:

  1. Being able to have multi-page Forms is good and should be maintained! But it’s not the usual scenario so it should be easier to create simple Forms (e.g. by having the elements of the first page directly underneath the form and rename the “pages” collection to “further pages”)
  2. Currently all forms in Neos are uncached. At least the node based definition should be cached though. I started a PR that will allow for the form to be cached for the initial request, but that might need more fine tuning
  3. In order to be able to select the form elements in the Neos Backend, they need to be annotated (Neos.Neos:ContentElementWrapping). For this to work the Form package needs to be extended so that Neos can hook into the rendering. A corresponding PR is in the working
  4. For multi-page Forms the existing FormBuilder uses AJAX to render the selected page upon request. This is not (easily) possible with the Neos UI. I would suggest to render the whole form instead in the backend. We could hide “inactive” pages via JavaScript. I’ll prepare another PR to the Form Package that allows it to render the whole Form.

Timeframe

I plan to have the two packages usable by the end of next week (21. July) and released in the following week.
So now is your chance to prevent me from doing something stupid - or rather to get your ideas/requirements considered :wink:
I’ll do a quick hangout tomorrow (probably 10:15am CET) with @martoro and everyone who is interested. So feel free to join us and/or comment this post.

PS: Thanks a lot to @sebastian for some (as usual) very helpful Fusion advices!

3 Likes

My 2 cents, form.pages, can be renamed form.elements and basically a page is specific element that add the multi page rendering but it’s optional. Like this the semantic of the form is OK with or without page.

It’s a really important step for Neos to have this kind of feature, thanks to work on it, and thanks to the sponsors.

Yes, in the Form Framework a Page is already an Element, too. And it is also a so called “Section” element that can have child elements. But it has some special semantics to it, so internally there’s a difference between appending a page or an element to the form…

Anyways, very good idea. In the UI we could allow elements and pages to be added on the same level. I just wonder if that’s maybe more confusing, i.e. what happens if you insert a “normal” element after a page?

IMO this structure is a good compromise:

form (Neos.Form:Form)
  elements (ElementCollection)
    name (Neos.Form:SingleLineText)
      validators (ValidatorCollection)
    email (Neos.Form:SingleLineText)
      validators (ValidatorCollection)
  furtherPages (PageCollection)
    page2 (Neos.Form:Page)
      elements (ElementCollection)
        foo (Neos.Form:SingleLineText)
          validators (ValidatorCollection)
  finishers (FinisherCollection)

Or in fusion something like

formDefinition = Neos.Form:FormDefinition {
    elements = Neos.Fusion:Array {
        name = Neos.Form:SingleLineText {
            # ...
        }
        email = Neos.Form:SingleLineText {
            # ...
        }
    }
    furtherPages = Neos.Fusion:Array {
        page2 = Neos.Form:Page {
            elements = Neos.Fusion:Array {
                foo = Neos.Form:SingleLineText
            }
        }
    }
}

do you agree?

form (Neos.Form:Form)
  pages (ContentCollection)
    page1 (Neos.Form:Page)

Finally I prefer the first version, consistent and easy to document. Basically a simple form contain a single page. I think it’s better than the furtherPages …

Actually looking at that Fusion code, in my opinion i like having Fluid Templates for the HTML Markup more than that crude Fusion Code. After all thats what Fluid is made for. Isn’t it?

Other than that, thanks for working on it and thanks to the sponsors. :wink:

A perfect candidate for AFX :wink: the template are so slim, and can be really nice to have the possibility to override the rendering of an element with prototype overloading (like change the rendering a bit, if rendered in a certain wrapper. That’s where Fusion is more flexible than fusion (or you just duplicated the template, but hard to name Input.html -> InputInCertainContext.html)

Hi all.

Thanks a lot for your feedback and sorry for the lack of response. I have worked on this quite a bit now and discussed several approaches with many of you.
Hopefully I’ll be able to move the two packages to the Neos GitHub Repos and draft a first release today.

I did go for the furtherPages approach now after going with this forth and back.
For the regular one-page-form this makes things a whole lot easier and less cluttered and for multi-page forms there’s almost no difference.

In any case this is just a couple of Fusion mapping that can be changed if needed (or split up into more NodeTypes for more complex forms).

Fair enough, I think it’s a matter of taste really and it’s just an additional package now that you’re free not to install :wink:

By these standards we wouldn’t have cars today because that’s what horse carts are made for :slight_smile:
No, but seriously, I hope that we can at some point map Fusion prototypes to Web Components directly moving all presentation logic to the Frontend. But that’s a different story…

1 Like