Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Form arrays are a tight coupling of three concepts:
- Schema: The fields represented by the form, their title, and their data type. Also includes validation definitions like "minimum value".
- UI: The presentational information, like "radios vs select list". Also includes whether the element is disabled.
- Data: The current values of the form (either default values, or the live values as they are changed by the user).
Separating these concerns will be essential to moving any form rendering to the client side.
Proposed resolution
Using https://github.com/mozilla-services/react-jsonschema-form as a target, write a layer to parse existing FAPI arrays into each of the three components needed: schema, uiSchema, formData.
Find all of the pain points that need fixing on the Drupal side.
A companion issue should be opened to work on deciding if the existing React project should be extended or replaced to handle Drupal's custom needs.
Remaining tasks
TBD
User interface changes
N/A (ideally!)
API changes
TBD
Data model changes
N/A
Comment | File | Size | Author |
---|---|---|---|
#9 | 2913372-jsonschema_form-9.patch | 25.51 KB | tim.plunkett |
Comments
Comment #2
GrandmaGlassesRopeManI can't read.Comment #3
GrandmaGlassesRopeManComment #4
dawehnerI totally love this separation. Its how all modern form system work, unlike Drupal which has merged everything together historically.
One question I ask myself though: Does this mean forms will be always defined in PHP? Where is the line drawn between forms in JS and in PHP.
Comment #5
larowlanI have a form factory I wrote in d7...that could be ported to d8 easily
This is how you build a form for a plain old php object.
then the fapi array is just
Where
$distance
is an object of class Distance.@options
supports both an array and a reference ID that looks up a list in an options provider (they are defined dynamically).The element value and process callbacks take care of making
$form['distance']
into a tree of fields, and then converting the value into an object - which relies on a normalizer/denormalizer for converting to and from and array/object.I think it would allow us to take some steps along this path.
Comment #6
mrjmd CreditAttribution: mrjmd commented+1 to this idea, exciting stuff.
I experimented some with using a similar model in Contenta Angular, where the schemas exposed by Open API would automatically generate editorial forms for creating / editing nodes in Angular. It worked alright for simple forms, but of course choked on custom Drupal field types like entity reference. Deep integration with FAPI would solve that issue, and also expose all forms, not just entities.
With regards to #4, in my view this is an incremental step towards opening up possibilities down the line. By moving forward with this separation, field types and forms defined in Drupal's backend can reliably maintain their status as the canonical driver of UI's, without client side applications having to write code every time a field is added or modified.
Once this separation exists, down the line the responsibility for defining and configuring these field types and forms could theoretically be shifted to JS. And of course whenever it's desirable a client side application can still customize their forms and simply rely on posting back through Drupal's existing APIs (while assuming the burden of extra lift when data models change).
Comment #7
phenaproxima+1 for this idea as well. For whatever time I can spare, you have my sword.
Comment #8
travist CreditAttribution: travist as a volunteer commentedI would like to propose the Form.io Vanilla JS renderer @ https://formio.github.io/formio.js. This will work in all frameworks so does not require React, but one could easily use https://github.com/formio/react-formio for a React.js wrapper.
A Form builder is also provided @ http://formio.github.io/ngFormBuilder
All libraries are MIT licensed, stand-alone, and do not require the hosted Form.io service to work.
Disclaimer: I work for Form.io, and myself and my team wrote these libraries.
Comment #9
tim.plunkettThat form builder is really cool. Reminds me of the UI that was built for Webforms that one time.
Anyway, here's the code I had locally from 2 months ago, whoops.
Super rough, but I need to stop sitting on it.
This is very focused on translating what we currently have in the Form API.
Not sure what the next concrete step should be, but I wanted to look at ways for existing forms to provide some sort of split in the 3 concepts.
Comment #10
tim.plunkettLocally I hacked up https://github.com/mozilla-services/react-jsonschema-form/blob/master/pl... and put this in the top:
Comment #11
larowlanNice to see some movement here
i think embedding knowledge of the element types here is not the right approach. I think we should add a new interface JsonSchemaEnabledFormElementInterface with the required methods, and then add to each Element plugin as required. If an element doesn't support it, its dropped from the built schema or we fall through to the exception. There are a wide range of Element plugins in contrib that we cannot know about in advance. If those elements don't support it, they'd fall through to the exception like you have here. tl;dr invert this - make the elements tell the builder about their features and not the other way
same here, we can't special case just those two - there might be others in contrib
same deal here
in my opinion this should be separated out of this class into a separate service - i've done similar in d7 and it would be useful as a stand alone.
Comment #12
tim.plunkettI agree!
I also agree!
Comment #13
dawehnerI totally agree that conceptually, even just on the php side, separating the data, schema and UI makes more than sense.
One question I have is: Do you plan to implement all the feature of FAPI, like multi-page forms, or would you rather say: There is a limited scope where using a non custom form written in JS makes sense.
Comment #14
Wim LeersCool stuff.
I think the biggest challenge is validation. We'd need to be able to make all our validation logic work on the client side somehow. For some things that will be easy (min/max value, list of allowed values), but for lots of things, that will be very hard.
IOW, I completely agree asking this question is important:
Comment #15
dawehnera) I question whether the additional roundtrip for validation is a huge issue for generic forms. The time needed to validate could still be within a reasonable user experience when done serverside.
b) If we accept its a problem,. using formapi as base could be a bit of a problem, due to its insane flexibility. If we otherwis start with typed data, we would already have a way of validation. If you look at the structure of a constraint definition:
you could replicate that in JS. Mapping from the form elements to the data could be easier at the end of the day.
Comment #16
tim.plunkettAlong the same lines, I thought about mimicking the work done in http://www.drupal.org/project/config_inspector to generate forms out of config schema.
Even though most config forms are the least exciting forms in Drupal, they are often the most predictable.
Comment #17
Wim Leers#15: agreed on both counts. Especially b) is the direction I've been thinking in: declarative constraints rather than code (i.e. a layer of indirection), which allows for equivalent JS implementations.
Comment #18
Wim Leers#16: you mentioning config forms here makes me ridiculously happy/excited, because #2300677: JSON:API POST/PATCH support for fully validatable config entities is blocked on:
to become a reality. And with @tim.plunkett helping out with that stuff, things could go much faster.
Comment #19
effulgentsia CreditAttribution: effulgentsia at Acquia commentedRaising to Major for this.
Comment #20
e0ipsoIt seems that this has not been mentioned in the issue, but we have a dedicated tool that generates JSON Schemas for entity types. Granted these will not be the only schemas we'll need to generate, we still want arbitrary front-end forms to be generated. However, I feel that schemas should describe data types, and forms should be generated from Schema + Form definition. See slide #46 in a presentation I did in DrupalCon Dublin for more context of what I mean.
The module that generates schemas is called Schemata and is used to generate automatic documentation for the API-First initiative via OpenAPI. Note that OpenAPI/Swagger generates forms for the API resources.
In any case, I'm not opposed to changing the way the API-First initiative recommends generating schemas, but I would really like to have a single and robust schema generation tool that can be used in different scenarios.
Comment #21
dawehner@e0ipso's comment is interesting.
It basically comes down to, due to treat Forms as the starting point or do we put our data as starting point aka. our entities. This issue would allow any form to be exposed, while something like schemata totally just works for entities, and is in that scope basically what I was talking partially about in #15
Comment #22
e0ipsoIf we think it's a good direction we can put the data at the starting point even for non-entities. If a user is filling a form with the intention to upsert some data, then we should start by describing that data. Even if that is not an entity.
If the form has nothing to do with data in the server we can either have the form completely managed in React, or still send a pair of Schema + Form definition with an empty schema. I think I like the form managed in React better, because the submission may not need to interact with the server.
Comment #23
phenaproximaI tend to agree with this. At the end of the day, we are working with data. It's not necessarily as complex as entity data, but it's data nonetheless. I think a data-first model is appropriate.
Comment #24
effulgentsia CreditAttribution: effulgentsia at Acquia commentedLooks like that module already bases its work on TypedData. Via code we already have in
TypedConfigManagerInterface
andDrupal\Core\Config\Schema
, I think that means it would work for all our config data as well (both config entities and single configs). And for forms that upsert data that is neither content entities nor config, we could probably create TypedData objects to represent whatever their data is as well. Based on current experience with Schemata, have any problems surfaced with TypedData as the foundation on which to generate JSON schemas?Comment #25
e0ipsoYeah, I was thinking about the same thing. That may not be the most pleasant DX for writing a form, but I guess that we can work on DX at some point later.
Comment #27
alex.mazaltovTwo month ago last comment for this issue.
Does it mean discussion closed?
What have been done in this direction?
Global codedesprint should bring some progress.
Comment #28
tim.plunkettThis discussion is not closed.
Unassigning myself for now, but I'm still interested in this effort! Also once again, not attached to the above patch at all. It was just an interesting exploration.
Comment #29
AaronMcHaleI'm writing following on from a short discussion in Slack about how we should replace all form element arrays with Objects, so instead of:
We could have:
The two main advantages to this is that it aligns with the move to OO and for those like myself who use an IDE like PhpStorm it makes life easier by allowing the IDE to suggest the various properties that you can use to build the form.
So I think the first thing we need to do is agree that switching to OO would be the best starting point, possibly create a child issue to establish the standards for how OO Form Elements will be constructed.
Moving forward with that in mind we can look at the separation of Schema, UI and Data.
Fundamentally when it comes down to it, yes forms just handle data and yes we could do something with the headless/API side of stuff, but ultimately defining a form and the data it accepts is so intertwined with what the actual form element is and looks like, so there might be challenges to overcome there. You could abstract radio buttons, select lists, maybe even check boxes into some kind of "choice" class but we'd have to be very careful to make sure we don't end up breaking some form logic.
Going on that line of thinking you could break this "choice" class down into two classes: "Single Choice" and "Multi Choice". The "Single Choice" class could be displayed as a set of Radio Buttons or a Select List, but the key factor is it will only accept one selection. The "Multi Choice" class could be displayed as Check Boxes or a Select List (which accepts multiple selections), but the key factor is will accept more than one selection.
From a headless perspective that could work if implemented correctly in the API, but we should be careful not to implement something like this in a way that negatively impacts developers, the last thing I want is to have to write more arrays or hooks or create more instances of classes just to add a text box to a form.
To achieve this level of abstraction while still maintaining simplicity my OO example from earlier could be adapted to look like this:
There are two key differences here:
The advantages to this approach are:
Some other examples of this:
Notice I did not include a ->size() (the equivalent of '#size') this is because we should simply leave that up to the theme to define in CSS. To be honest the HTML "size" property should really just bee removed, not sure why it's still around, but that's a conversation for another day and place.
I'll leave it at those examples but you get the idea.
In summary:
Thanks
Aaron
Comment #30
sinasalek CreditAttribution: sinasalek at Practicalidea commented@AaronMcHale well written.
We also have a very good entity api in d8 core with almost completely separated layers. Each entity/field has data storage(Data), form(Schema) and display(UI). I wonder if we can leverage that and extend it to all forms. I'm talking about fields without storage and ability to create a form using only OO like what explained by @AaronMcHale. I think it would be much easier to implement and lots of code can be reused. I think there has been a discussion about using field api in form, i haven't been able to find it yet.
Comment #31
AaronMcHaleInteresting idea, I like the idea of being able to reuse code between the Entity/Field and Form APIs, we'd need to first figure out if that would be feasible and what challenges would need to be overcome. As you pointed to, and for me ultimately my main concern is that anything like that doesn't create more work for developers like ourselves when it comes to simply creating or modifying forms, so we should keep that in mind.
What I suspect we might find or at least the recommendation would be that the base code goes into the Form API and the Field/Entity APIs just builds on top of the Form API. To me that also makes the most sense since the Filed API is basically just dealing with forms but providing a more managed approach and a UI.
I've also been thinking about how we'd go about implementing something like this, as forms are probably one of the most widely used parts of Drupal so changing their structure presents two possibilities: either the work would need to be done in a major release like 9.x, or we'd need to find a way to maintain the current system while introducing this new one, then deprecating the old system.
Personally I'm in favour of the latter option as it means we can stay aligned to the philosophy of a constantly evolving Drupal where upgrading between versions is seamless for developers and site builders.
To achieve the latter option though we may also want to do it in tandem with introducing a new OO based Hooks or Events system. So if you're using the old hooks you use the old Form Arrays and if you're using the new OO Hooks/Events you use the new OO Forms. Then we just link the two together on the backend and have form arrays converted to objects as and when the system finds them.
I think this approach would work well and would be the least disruptive to current development and Drupal sites, since it allows for a period of time for people to migrate. If we did want to push for this we'd need to spin off some new issues as this is beginning to look like a rather large project.
Comment #35
effulgentsia CreditAttribution: effulgentsia at Acquia commentedYep, the new system can be added in any Drupal 9 minor. The old system can be deprecated once all of core is converted to using the new system. And then the old system can be removed from the following major version (e.g., Drupal 10 if all this is done by one or two minors prior to Drupal 10's release).
Comment #42
Wim LeersRelated: #2949888: Enhance config schema for richer default experiences.
Comment #43
fgmRe #16 (I only just noticed this issue). One thing I've been doing for a number of years, inspired by Config Inspector indeed is use the schema label for the config form element title AND, where available after a delimiter split, for the config form element description.
It would be nice if schema could be extended to support description separately from label, for this kind of autogeneration of edit forms.
Comment #44
Jelle_SRE #14:
I'm about 6 years late to reply, but I've been a maintainer of the Clientside Validation module. Admittedly, I haven't been active on that project for quite some time, others have taken over, but it might me a good starting point/inspiration for the validation of forms on the JS side.