Moved FormFieldSchemaTrait into FormField

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 https://github.com/silverstripe/silverstripe-framework/issues/4938
See http://php.net/manual/en/language.oop5.traits.php#language.oop5.traits.properties.example
This commit is contained in:
Ingo Schommer 2016-03-28 21:52:51 +13:00
parent c3a8159e80
commit 3862a7a0a7
2 changed files with 198 additions and 214 deletions

View File

@ -20,13 +20,15 @@
* For example, data might be saved to the filesystem instead of the data record, or saved to a
* component of the data record instead of the data record itself.
*
* A form field can be represented as structured data through {@link FormSchema},
* including both structure (name, id, attributes, etc.) and state (field value).
* Can be used by for JSON data which is consumed by a front-end application.
*
* @package forms
* @subpackage core
*/
class FormField extends RequestHandler {
use SilverStripe\Forms\Schema\FormFieldSchemaTrait;
/**
* @var Form
*/
@ -166,6 +168,57 @@ class FormField extends RequestHandler {
*/
protected $attributes = [];
/**
* The data type backing the field. Represents the type of value the
* form expects to receive via a postback.
*
* The values allowed in this list include:
*
* - String: Single line text
* - Hidden: Hidden field which is posted back without modification
* - Text: Multi line text
* - HTML: Rich html text
* - Integer: Whole number value
* - Decimal: Decimal value
* - MultiSelect: Select many from source
* - SingleSelect: Select one from source
* - Date: Date only
* - DateTime: Date and time
* - Time: Time only
* - Boolean: Yes or no
* - Custom: Custom type declared by the front-end component. For fields with this type,
* the component property is mandatory, and will determine the posted value for this field.
* - Structural: Represents a field that is NOT posted back. This may contain other fields,
* or simply be a block of stand-alone content. As with 'Custom',
* the component property is mandatory if this is assigned.
*
* @var string
*/
protected $schemaDataType;
/**
* The type of front-end component to render the FormField as.
*
* @var string
*/
protected $schemaComponent;
/**
* Structured schema data representing the FormField.
* Used to render the FormField as a ReactJS Component on the front-end.
*
* @var array
*/
protected $schemaData = [];
/**
* Structured schema state representing the FormField's current data and validation.
* Used to render the FormField as a ReactJS Component on the front-end.
*
* @var array
*/
protected $schemaState = [];
/**
* Takes a field name and converts camelcase to spaced words. Also resolves combined field
* names with dot syntax to spaced words.
@ -1290,4 +1343,147 @@ class FormField extends RequestHandler {
return $this->dontEscape;
}
/**
* Sets the component type the FormField will be rendered as on the front-end.
*
* @param string $componentType
* @return FormField
*/
public function setSchemaComponent($componentType) {
$this->schemaComponent = $componentType;
return $this;
}
/**
* Gets the type of front-end component the FormField will be rendered as.
*
* @return string
*/
public function getSchemaComponent() {
return $this->schemaComponent;
}
/**
* Sets the schema data used for rendering the field on the front-end.
* Merges the passed array with the current `$schemaData` or {@link getSchemaDataDefaults()}.
* Any passed keys that are not defined in {@link getSchemaDataDefaults()} are ignored.
* If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
*
* @param array $schemaData - The data to be merged with $this->schemaData.
* @return FormField
*
* @todo Add deep merging of arrays like `data` and `attributes`.
*/
public function setSchemaData($schemaData = []) {
$current = $this->getSchemaData();
$this->schemaData = array_merge($current, array_intersect_key($schemaData, $current));
return $this;
}
/**
* Gets the schema data used to render the FormField on the front-end.
*
* @return array
*/
public function getSchemaData() {
return array_merge($this->getSchemaDataDefaults(), $this->schemaData);
}
/**
* @todo Throw exception if value is missing, once a form field schema is mandatory across the CMS
*
* @return string
*/
public function getSchemaDataType() {
return $this->schemaDataType;
}
/**
* Gets the defaults for $schemaData.
* The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaData()} are ignored.
* Instead the `data` array should be used to pass around ad hoc data.
*
* @return array
*/
public function getSchemaDataDefaults() {
return [
'name' => $this->getName(),
'id' => $this->ID(),
'type' => $this->getSchemaDataType(),
'component' => $this->getSchemaComponent(),
'holder_id' => null,
'title' => $this->Title(),
'source' => null,
'extraClass' => $this->ExtraClass(),
'description' => $this->getDescription(),
'rightTitle' => $this->RightTitle(),
'leftTitle' => $this->LeftTitle(),
'readOnly' => $this->isReadOnly(),
'disabled' => $this->isDisabled(),
'customValidationMessage' => $this->getCustomValidationMessage(),
'attributes' => [],
'data' => [],
];
}
/**
* Sets the schema data used for rendering the field on the front-end.
* Merges the passed array with the current `$schemaData` or {@link getSchemaDataDefaults()}.
* Any passed keys that are not defined in {@link getSchemaDataDefaults()} are ignored.
* If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
*
* @param array $schemaData - The data to be merged with $this->schemaData.
* @return FormField
*
* @todo Add deep merging of arrays like `data` and `attributes`.
*/
public function setSchemaState($schemaState = []) {
$current = $this->getSchemaState();
$this->schemaState = array_merge($current, array_intersect_key($schemaState, $current));
return $this;
}
/**
* Gets the schema state used to render the FormField on the front-end.
*
* @return array
*/
public function getSchemaState() {
return array_merge($this->getSchemaStateDefaults(), $this->schemaState);
}
/**
* Gets the defaults for $schemaState.
* The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaState()} are ignored.
* Instead the `data` array should be used to pass around ad hoc data.
* Includes validation data if the field is associated to a {@link Form},
* and {@link Form->validate()} has been called.
*
* @return array
*/
public function getSchemaStateDefaults() {
$field = $this;
$form = $this->getForm();
$validator = $form ? $form->getValidator() : null;
$errors = $validator ? (array)$validator->getErrors() : [];
$messages = array_filter(array_map(function($error) use ($field) {
if($error['fieldName'] === $field->getName()) {
return [
'value' => $error['message'],
'type' => $error['messageType']
];
}
}, $errors));
return [
'id' => $this->ID(),
'value' => $this->Value(),
'valid' => (count($messages) === 0),
'messages' => (array)$messages,
'data' => [],
];
}
}

View File

@ -1,212 +0,0 @@
<?php
namespace SilverStripe\Forms\Schema;
use Exception;
/**
* Class FormFieldSchemaTrait
* @package SilverStripe\Forms\Schema
*
* Allows {@link FormField} to be represented as structured data,
* including both structure (name, id, attributes, etc.) and state (field value).
* Can be used by {@link FormSchema} to represent a form in JSON,
* to be consumed by a front-end application.
*
* WARNING: Experimental API.
*/
trait FormFieldSchemaTrait {
/**
* The data type backing the field. Represents the type of value the
* form expects to receive via a postback.
*
* The values allowed in this list include:
*
* - String: Single line text
* - Hidden: Hidden field which is posted back without modification
* - Text: Multi line text
* - HTML: Rich html text
* - Integer: Whole number value
* - Decimal: Decimal value
* - MultiSelect: Select many from source
* - SingleSelect: Select one from source
* - Date: Date only
* - DateTime: Date and time
* - Time: Time only
* - Boolean: Yes or no
* - Custom: Custom type declared by the front-end component. For fields with this type,
* the component property is mandatory, and will determine the posted value for this field.
* - Structural: Represents a field that is NOT posted back. This may contain other fields,
* or simply be a block of stand-alone content. As with 'Custom',
* the component property is mandatory if this is assigned.
*
* @var string
*/
protected $schemaDataType;
/**
* The type of front-end component to render the FormField as.
*
* @var string
*/
protected $schemaComponent;
/**
* Structured schema data representing the FormField.
* Used to render the FormField as a ReactJS Component on the front-end.
*
* @var array
*/
protected $schemaData = [];
/**
* Structured schema state representing the FormField's current data and validation.
* Used to render the FormField as a ReactJS Component on the front-end.
*
* @var array
*/
protected $schemaState = [];
/**
* Sets the component type the FormField will be rendered as on the front-end.
*
* @param string $componentType
* @return FormField
*/
public function setSchemaComponent($componentType) {
$this->schemaComponent = $componentType;
return $this;
}
/**
* Gets the type of front-end component the FormField will be rendered as.
*
* @return string
*/
public function getSchemaComponent() {
return $this->schemaComponent;
}
/**
* Sets the schema data used for rendering the field on the front-end.
* Merges the passed array with the current `$schemaData` or {@link getSchemaDataDefaults()}.
* Any passed keys that are not defined in {@link getSchemaDataDefaults()} are ignored.
* If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
*
* @param array $schemaData - The data to be merged with $this->schemaData.
* @return FormField
*
* @todo Add deep merging of arrays like `data` and `attributes`.
*/
public function setSchemaData($schemaData = []) {
$current = $this->getSchemaData();
$this->schemaData = array_merge($current, array_intersect_key($schemaData, $current));
return $this;
}
/**
* Gets the schema data used to render the FormField on the front-end.
*
* @return array
*/
public function getSchemaData() {
return array_merge($this->getSchemaDataDefaults(), $this->schemaData);
}
public function getSchemaDataType() {
if ($this->schemaDataType == null) {
throw new Exception('You need to set a schemaDataType on ' . $this->getName() . ' field');
}
return $this->schemaDataType;
}
/**
* Gets the defaults for $schemaData.
* The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaData()} are ignored.
* Instead the `data` array should be used to pass around ad hoc data.
*
* @return array
*/
public function getSchemaDataDefaults() {
return [
'name' => $this->getName(),
'id' => $this->ID(),
'type' => $this->getSchemaDataType(),
'component' => $this->getSchemaComponent(),
'holder_id' => null,
'title' => $this->Title(),
'source' => null,
'extraClass' => $this->ExtraClass(),
'description' => $this->getDescription(),
'rightTitle' => $this->RightTitle(),
'leftTitle' => $this->LeftTitle(),
'readOnly' => $this->isReadOnly(),
'disabled' => $this->isDisabled(),
'customValidationMessage' => $this->getCustomValidationMessage(),
'attributes' => [],
'data' => [],
];
}
/**
* Sets the schema data used for rendering the field on the front-end.
* Merges the passed array with the current `$schemaData` or {@link getSchemaDataDefaults()}.
* Any passed keys that are not defined in {@link getSchemaDataDefaults()} are ignored.
* If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
*
* @param array $schemaData - The data to be merged with $this->schemaData.
* @return FormField
*
* @todo Add deep merging of arrays like `data` and `attributes`.
*/
public function setSchemaState($schemaState = []) {
$current = $this->getSchemaState();
$this->schemaState = array_merge($current, array_intersect_key($schemaState, $current));
return $this;
}
/**
* Gets the schema state used to render the FormField on the front-end.
*
* @return array
*/
public function getSchemaState() {
return array_merge($this->getSchemaStateDefaults(), $this->schemaState);
}
/**
* Gets the defaults for $schemaState.
* The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaState()} are ignored.
* Instead the `data` array should be used to pass around ad hoc data.
* Includes validation data if the field is associated to a {@link Form},
* and {@link Form->validate()} has been called.
*
* @return array
*/
public function getSchemaStateDefaults() {
$field = $this;
$form = $this->getForm();
$validator = $form ? $form->getValidator() : null;
$errors = $validator ? (array)$validator->getErrors() : [];
$messages = array_filter(array_map(function($error) use ($field) {
if($error['fieldName'] === $field->getName()) {
return [
'value' => $error['message'],
'type' => $error['messageType']
];
}
}, $errors));
return [
'id' => $this->ID(),
'value' => $this->Value(),
'valid' => (count($messages) === 0),
'messages' => (array)$messages,
'data' => [],
];
}
}