Form field schema state

See https://github.com/silverstripe/silverstripe-framework/issues/4938
This commit is contained in:
Ingo Schommer 2016-03-01 16:15:47 +13:00
parent 746322a9f1
commit e1fcc64c41
5 changed files with 203 additions and 18 deletions

View File

@ -166,21 +166,6 @@ class FormField extends RequestHandler {
*/
protected $attributes = [];
/**
* 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 = [];
/**
* Takes a field name and converts camelcase to spaced words. Also resolves combined field
* names with dot syntax to spaced words.

View File

@ -4,6 +4,29 @@ namespace SilverStripe\Forms\Schema;
trait FormFieldSchemaTrait {
/**
* 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.
*
@ -58,7 +81,7 @@ trait FormFieldSchemaTrait {
*
* @return array
*/
function getSchemaDataDefaults() {
public function getSchemaDataDefaults() {
return [
'type' => $this->class,
'component' => $this->getSchemaComponent(),
@ -78,4 +101,63 @@ trait FormFieldSchemaTrait {
'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

@ -45,7 +45,7 @@ class FormSchema {
/**
* Gets the current state of this form as a nested array.
*
* @param From $form
* @param Form $form
* @return array
*/
public function getState(Form $form) {
@ -55,6 +55,17 @@ class FormSchema {
'messages' => []
];
foreach ($form->Fields()->dataFields() as $field) {
$state['fields'][] = $field->getSchemaState();
}
if($form->Message()) {
$state['messages'][] = [
'value' => $form->Message(),
'type' => $form->MessageType(),
];
}
return $state;
}
}

View File

@ -278,6 +278,37 @@ class FormFieldTest extends SapphireTest {
$schema = $field->getSchemaData();
$this->assertEquals(array_key_exists('myCustomKey', $schema), false);
}
public function testGetSchemaState() {
$field = new FormField('MyField');
$field->setValue('My value');
$schema = $field->getSchemaState();
$this->assertEquals('My value', $schema['value']);
}
public function testSetSchemaState() {
$field = new FormField('MyField');
// Make sure the user can update values.
$field = $field->setSchemaState(['value' => 'My custom value']);
$schema = $field->getSchemaState();
$this->assertEquals($schema['value'], 'My custom value');
// Make user the user can't define custom keys on the schema.
$field = $field->setSchemaState(['myCustomKey' => 'yolo']);
$schema = $field->getSchemaState();
$this->assertEquals(array_key_exists('myCustomKey', $schema), false);
}
public function testGetSchemaStateWithFormValidation() {
$field = new FormField('MyField');
$validator = new RequiredFields('MyField');
$form = new Form(new Controller(), 'TestForm', new FieldList($field), new FieldList(), $validator);
$validator->validationError('MyField', 'Something is wrong', 'error');
$schema = $field->getSchemaState();
$this->assertEquals(count($schema['messages']), 1);
$this->assertEquals('Something is wrong', $schema['messages'][0]['value']);
}
}
/**

View File

@ -55,7 +55,83 @@ class FormSchemaTest extends SapphireTest {
$formSchema = new FormSchema();
$expected = [
'id' => 'TestForm',
'fields' => [],
'fields' => [
[
'id' => 'Form_TestForm_SecurityID',
'value' => $form->getSecurityToken()->getValue(),
'messages' => [],
'valid' => true,
'data' => []
]
],
'messages' => []
];
$state = $formSchema->getState($form);
$this->assertInternalType('array', $state);
$this->assertJsonStringEqualsJsonString(json_encode($expected), json_encode($state));
}
public function testGetStateWithFormMessages() {
$fields = new FieldList();
$actions = new FieldList();
$form = new Form(new Controller(), 'TestForm', $fields, $actions);
$form->sessionMessage('All saved', 'good');
$formSchema = new FormSchema();
$expected = [
'id' => 'TestForm',
'fields' => [
[
'id' => 'Form_TestForm_SecurityID',
'value' => $form->getSecurityToken()->getValue(),
'messages' => [],
'valid' => true,
'data' => []
]
],
'messages' => [
[
'value' => 'All saved',
'type' => 'good'
]
]
];
$state = $formSchema->getState($form);
$this->assertInternalType('array', $state);
$this->assertJsonStringEqualsJsonString(json_encode($expected), json_encode($state));
}
public function testGetStateWithFieldValidationErrors() {
$fields = new FieldList(new TextField('Title'));
$actions = new FieldList();
$validator = new RequiredFields('Title');
$form = new Form(new Controller(), 'TestForm', $fields, $actions, $validator);
$form->loadDataFrom([
'Title' => 'My Title'
]);
$validator->validationError('Title', 'Title is invalid', 'error');
$formSchema = new FormSchema();
$expected = [
'id' => 'TestForm',
'fields' => [
[
'id' => 'Form_TestForm_Title',
'value' => 'My Title',
'messages' => [
['value' => 'Title is invalid', 'type' => 'error']
],
'valid' => false,
'data' => []
],
[
'id' => 'Form_TestForm_SecurityID',
'value' => $form->getSecurityToken()->getValue(),
'messages' => [],
'valid' => true,
'data' => []
]
],
'messages' => []
];