Skip to content
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

Form schema fixes #5239

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 198 additions & 2 deletions forms/FormField.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so this comment is about this entire commit but I have to say that this is quite surprising. I don't see the fact that the trait is used in 1 place as a reason not to break the code into smaller pieces.

Perhaps the trait should mark abstract function getSchemaDataType(), and the implementation of only that method is pulled into FormField?

*/
class FormField extends RequestHandler {

use SilverStripe\Forms\Schema\FormFieldSchemaTrait;

/**
* @var Form
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1290,4 +1343,147 @@ public function getDontEscape() {
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' => [],
];
}

}
Loading