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.
- Create a new Package (name tbd, maybe
Neos.Form.FusionPreset
) - Configure a new preset
fusion
that can be used/extended for custom forms - Define Fusion prototypes for all default Form element types
- 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:
- 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”)
- 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
- 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 - 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
I’ll do a quick hangout tomorrow (probably 10:15am CET) with @mficzel 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!