-
Notifications
You must be signed in to change notification settings - Fork 824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: FormField React Integration API #4938
Comments
I really like this direction. For the FormField configuration my vote would be option B (explicit getters and setters). +1 for me on everything here. |
Read through this yesterday and have the exact same opinion as @markguinn - everything outlined here looks good, and explicit getters/setters are my preference too. I’m still a bit fuzzy on the React side of things as I’m very early in that learning process, but my assumption is that this would allow you to create a new component and do: $field = TextField::create('MyTextField')
->setSchemaComponent('MySuperAwesomeTextFieldComponent') How do (or rather, “do”) schema components tie in with specific $fieldA = DropdownField::create('FieldA', null, ['A' => 'A', 'B' => 'B'])
->setSchemaComponent('OptionButtons');
$fieldB = OptionsetField::create('FieldB', null, ['A' => 'A', 'B' => 'B'])
->setSchemaComponent('OptionButtons'); Would both of these work and appear the same in the CMS? My guess is yes, as both field classes would likely provide the same schema info that the component could digest. Would the “Type” parameter for the schema be different for each of these two example classes, so they can have different default components? |
In order to make explicit setters, this means that Although we could add that as an Extension, in practise it will be an Extension that is applied to every FormField on every SS installation that uses the CMS, which is most of them. Rather than using an Extension, maybe a Trait is better? |
yes, although, if you're using that code you may as well just use two OptionsetFields with the default component (which I assume is OptionButtons). I think using a trait is fine, but it won't be overridable (or removable) as an extension is. |
Preparing for form schema API, see silverstripe/silverstripe-framework#4938
Preparing for form schema API, see silverstripe#4938
Preparing for form schema API, see silverstripe#4938
Preparing for form schema API, see silverstripe/silverstripe-framework#4938
Preparing for form schema API, see silverstripe#4938
Preparing for form schema API, see silverstripe/silverstripe-framework#4938
The RFC requires a FormField implementation to override $schemaDataType, but its defined on the trait - which can't be redefined by a field subclass. In the end, the trait was never designed to be reuseable on classes other than FormField. We need to admit that architecturally, we'll have to add all that API weight to the base FormField class because of the way forms are structured in SilverStripe (mainly due to a missing layer of indirection in getCMSFields implementations). Also implemented the $schemaDataType on fields where its known. See silverstripe#4938 See http://php.net/manual/en/language.oop5.traits.php#language.oop5.traits.properties.example
The RFC requires a FormField implementation to override $schemaDataType, but its defined on the trait - which can't be redefined by a field subclass. In the end, the trait was never designed to be reuseable on classes other than FormField. We need to admit that architecturally, we'll have to add all that API weight to the base FormField class because of the way forms are structured in SilverStripe (mainly due to a missing layer of indirection in getCMSFields implementations). Also implemented the $schemaDataType on fields where its known. See silverstripe#4938 See http://php.net/manual/en/language.oop5.traits.php#language.oop5.traits.properties.example
The RFC requires a FormField implementation to override $schemaDataType, but its defined on the trait - which can't be redefined by a field subclass. In the end, the trait was never designed to be reuseable on classes other than FormField. We need to admit that architecturally, we'll have to add all that API weight to the base FormField class because of the way forms are structured in SilverStripe (mainly due to a missing layer of indirection in getCMSFields implementations). Also implemented the $schemaDataType on fields where its known. See silverstripe#4938 See http://php.net/manual/en/language.oop5.traits.php#language.oop5.traits.properties.example
Is this one completed now? |
This has been partially implemented, to the state required for alpha 1. It hasn't been completely implemented yet. I've moved milestone to alpha 2 to track remaining portion, and will raise the process for closing an RFC with core team. |
https://groups.google.com/forum/#!topic/silverstripe-dev/bHE7sIK-fV8
#1. Introduction
1.1. Problem
When implementing React based front-end components, there is limited ability for forms declared via PHP to influence the front end. Likewise, the development of custom components used in front-end React based forms would require re-implementation of that behaviour via the traditional SilverStripe Forms API.
Since a key benefit of SilverStripe is that it is easy to tailor the UI to fit your site’s custom data model, this isn’t ideal.
It results in a bottleneck in development, where React forms cannot be developed effectively by developers who do not have both a strong front and back-end expertise, as well as the patience and time to solve several integration issues. It also means that client and server code is tightly coupled. Creating a new form field UI shouldn't necessitate creating a PHP subclass. And creating a new form field in PHP shouldn't require developers to create a new React component. The outcomes of this are slower development time, less flexibility, and buggy applications.
1.2. Goals
The goal of this RFC is to present a solution that addresses:
1.3. Proposed Solution
We propose that we create an intermediary data schema, in order to map an abstract representation of the form back-end, to the front-end rendering mechanism. This schema will be JSON structured data which can be extracted both from Form objects, as well as individual FormField instances.
The solution is based on an experimental CMS prototype ("Project Origami") developed at SilverStripe Ltd. https://github.com/silverstripe-supervillains/silverstripe-origami/blob/ac573256c0396f5fb5cf25f46625993651e14f55/code/extensions/OrigamiFormFieldExtension.php
Note: The intention of this prototype was to explore concepts rather than form the basis for a new CMS frontend and backend implementation.
All FormFields will be classified at two levels, in ways that can be interpreted by the front-end:
On the front-end, at least one default component will be declared for each FieldType, ensuring that any back-end form which follows this schema will be renderable.
In addition, multiple components can be built for each FieldType, to allow back-end form fields to further customise their appearance via the FieldComponent value.
Although the intention for this solution is to provide compatibility with React-based front-end components in a SilverStripe CMS context, it should also be usable with other javascript frameworks which respect the same schema in other contexts, as well as other front-end renderers such as native mobile apps
#2. Implementation
2.1. Form Field schema PHP API
FormField front-end schema will be applied to the existing Forms API via a set of standard injectable dependencies.
FormSchema.php
Used by LeftAndMain to return state / schema for a form.
LeftAndMain.php
URLs such as http://localhost/admin/modeladmin/edit/schema/1 (schema to edit object ID = '1') or http://ss40test.loc/admin/modeladmin/edit/schema (schema for creating a new record) can be passed to the frontend application.
Invoking this url will return a json formatted data structure with the top level attributes "schema" for the form schema, and "state" for the initial state of the form.
Form.php
CMSForm.php will be removed, and custom logic in Form::getValidationErrorResponse will allow a callback to be specified (injected via LeftAndMain.php).
FormField.php
In order to support user-code specification of field-specific options, some kind of api will be required on FormField.
Options are listed below in descending order of preference, with Option A being our preferred.
Option A - Option config
A setOption / getOption generic API will be added to FormField.php. This will be used, although not exclusively, by FormSchema to determine (for instance) the specific component to use for any one field.
For instance, when setting a custom template for a text field you could use the below API in your getCMSFields method:
Option B - Explicit schema getters / setters
Rather than add a generic options api, explicit getters or setters could be specified for each option. Eg...
Option C - Custom html attributes
Instead of adding a new mechanism, use get/setAttributes to hold custom values for the above options.
The weakness in this approach is that it will render any values specified via data- attributes in the resulting HTML.
Option D - Require custom subclasses
Rather than exposing additional customisation mechanisms to user-code, front-end customisation of formfields must instead be done via subclassing of FormFields.
E.g.
2.2. Schema Declaration
This declaration is based on the experimental schema declared at https://github.com/open-sausages/reactjs-prototype/tree/experiment/form-field-schema
2.2.1. Form schema
This JSON structure represents the schema of the form as a whole. Note that the schema of individual FormFields is included within the "fields" property of the containing form declaration, as well as "children" .
All content in this form is considered cacheable for the purposes of rendering that object's form in the future.
Notes on the above properties:
2.2.2. Form state
In addition to form schema, current state of the form must be representable on demand. This content is not cacheable, and covers:
The schema for the current form state is as below:
Notes on the above properties:
2.2.3. Routing behaviour
The above schema / state information will be emitted to the front end application in the following use cases:
What is actually returned from each of the above actions is determined by the value of X-FormSchema-Request http header. This header is a comma separated list (similar to X-Pjax) which lists one or both of 'schema' or 'state'. The resulting http response generated will return the requested value in the format below:
If only one of 'state' or 'schema' is requested, then no value for the omitted property will be returned.
In addition, the front-end application should respect responses containing the "Location" header, and should redirect the user (potentially discarding the current form) if requested.
2.2.4. Form validation declaration
Rules for form validation (e.g. for declaring certain fields as accepting urls only) is explicitly not declared as a part of this schema. It's possible in the future that a concise validation definition schema could be developed.
Within the bounds of the current schema, the following field attributes could still be explicitly assigned to make use of built in HTML5 field validation:
All other validation must still be performed and respected on the server-side during form postback.
2.2.5. Structural elements
Structural elements include (but are not limited to):
While structural elements may contain other data elements, they themselves have no underlying data property.
These field should have the type property specified as Structural, and must specify explicitly the front-end component used to represent it. E.g. 'Header', 'HTMLBlock', 'FieldGroup'.
Structural elements may, but are not required to, have nested fields declared under the children property.
Current composite fields (such as CurrencyField) could be implemented as structural components, with nested data components.
2.3. Text cast control
In certain cases, the back-end for form handlers may need to declare content as either plain text, or html. For example, any of the following properties could be either plain text or html:
By default, a literal value passed for any of these properties must be treated as raw text, and must be xml encoded before being put onto any template.
In order to declare a cast type (e.g. "html") then this can be specified using a nested json object. Note that newlines will be converted to HTML link breaks (
).
For instance, the following field has plain text title and rightTitle properties, but a html description property
2.4. Form Customisation
2.4.1. Customisation via PHP only
Within the front-end of the React application, the logic is able to infer from these data types which react component should be used. For instance, for a field specifying a type value "SingleSelect", a react "DropdownComponent" could be used by default. For any given react application, it should be possible to override the "default" component to use for each of the above types.
Furthermore, for any given type property there could be more than one available component. For example in the above case, if a "DropdownComponent" isn't the appropriate field template, the FormField PHP class could specify a component value of "RadioComponent". However, this assignment would result in a higher degree of coupling of the back-end with the front-end.
Back-end developers writing forms will not need to be concerned too much with the way the form appears, as the front-end promises to provide the necessary component scaffolding for the schema they choose.
Developers who are interested in tailoring the UI to be as optimal as possible should expect to specify "component" values that pick appropriate React-implemented fields, and/or to create new custom components for the UI.
SilverStripe templates will still be declared for form fields, but will only be used in UIs that are not based on React. React-driven UIs (initially the assets area, and in time likely the full CMS, as well as front-end applications that developers elect to build in React), won’t make reference to the .SS form field templates.
2.4.2. Customisation via both PHP and React
In order to better support advanced flexibility, it will be possible for the formfield schema to request a specific component that is custom built for a specific application. This would require the following:
2.4.3. Customisation via React only
Front-end developers should be able to develop new components, and for each specify the following:
If a new React component is developed to replace an existing component or abstract type, then any form field which previously specified the corresponding **type _or _component values would subsequently use the new component instead.
For a higher level overview of extending React components in SilverStripe see #4887
2.5. Security Considerations
2.5.1. CSRF
If the above form schema is adopted, it should automatically be included in any generated form. In addition, given that form handling for both React and traditional SilverStripe forms will use the same form action, it's expected that server-side validation will automatically ensure the token works consistently.
The text was updated successfully, but these errors were encountered: