From 3862a7a0a79428fdb18d4e4a5ae8c90381220596 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 28 Mar 2016 21:52:51 +1300 Subject: [PATCH] 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 --- forms/FormField.php | 200 ++++++++++++++++++++++++++++++- forms/FormFieldSchemaTrait.php | 212 --------------------------------- 2 files changed, 198 insertions(+), 214 deletions(-) delete mode 100644 forms/FormFieldSchemaTrait.php diff --git a/forms/FormField.php b/forms/FormField.php index e324ff78a..9b0711338 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -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' => [], + ]; + } + } diff --git a/forms/FormFieldSchemaTrait.php b/forms/FormFieldSchemaTrait.php deleted file mode 100644 index b267b4af4..000000000 --- a/forms/FormFieldSchemaTrait.php +++ /dev/null @@ -1,212 +0,0 @@ -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' => [], - ]; - } -}