From da9c133c1b294da9aaa580700353b6814e1d2c47 Mon Sep 17 00:00:00 2001 From: Jake Bentvelzen Date: Thu, 19 Jan 2017 21:00:25 +1100 Subject: [PATCH] ENHANCEMENT Add flattenFields() function to iterate over all deeply nested fields in a form --- src/Forms/FieldList.php | 107 ++++++++++++++++++++++++++++-- tests/php/Forms/FieldListTest.php | 87 ++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 4 deletions(-) diff --git a/src/Forms/FieldList.php b/src/Forms/FieldList.php index 21b9bd9d0..414ecc622 100644 --- a/src/Forms/FieldList.php +++ b/src/Forms/FieldList.php @@ -58,6 +58,38 @@ class FieldList extends ArrayList } } + /** + * Iterate over each field in the current list recursively + * + * @param callable $callback + */ + public function recursiveWalk(callable $callback) + { + $stack = $this->toArray(); + while (!empty($stack)) { + /** @var FormField $field */ + $field = array_shift($stack); + $callback($field); + if ($field instanceof CompositeField) { + $stack = array_merge($field->getChildren()->toArray(), $stack); + } + } + } + + /** + * Return a flattened list of all fields + * + * @return static + */ + public function flattenFields() + { + $fields = []; + $this->recursiveWalk(function (FormField $field) use (&$fields) { + $fields[] = $field; + }); + return static::create($fields); + } + /** * Return a sequential set of all fields that have data. This excludes wrapper composite fields * as well as heading / help text fields. @@ -66,8 +98,19 @@ class FieldList extends ArrayList */ public function dataFields() { - if (!$this->sequentialSet) { - $this->collateDataFields($this->sequentialSet); + if (empty($this->sequentialSet)) { + $fields = []; + $this->recursiveWalk(function (FormField $field) use (&$fields) { + if (!$field->hasData()) { + return; + } + $name = $field->getName(); + if (isset($fields[$name])) { + $this->fieldNameError($field, __FUNCTION__); + } + $fields[$name] = $field; + }); + $this->sequentialSet = $fields; } return $this->sequentialSet; } @@ -77,20 +120,76 @@ class FieldList extends ArrayList */ public function saveableFields() { - if (!$this->sequentialSaveableSet) { - $this->collateDataFields($this->sequentialSaveableSet, true); + if (empty($this->sequentialSaveableSet)) { + $fields = []; + $this->recursiveWalk(function (FormField $field) use (&$fields) { + if (!$field->canSubmitValue()) { + return; + } + $name = $field->getName(); + if (isset($fields[$name])) { + $this->fieldNameError($field, __FUNCTION__); + } + $fields[$name] = $field; + }); + $this->sequentialSaveableSet = $fields; } return $this->sequentialSaveableSet; } + /** + * Return array of all field names + * + * @return array + */ + public function dataFieldNames() + { + return array_keys($this->dataFields()); + } + + /** + * Trigger an error for duplicate field names + * + * @param FormField $field + * @param $functionName + */ + protected function fieldNameError(FormField $field, $functionName) + { + if ($this->form) { + $errorSuffix = sprintf( + " in your '%s' form called '%s'", + get_class($this->form), + $this->form->getName() + ); + } else { + $errorSuffix = ''; + } + + user_error( + sprintf( + "%s() I noticed that a field called '%s' appears twice%s", + $functionName, + $field->getName(), + $errorSuffix + ), + E_USER_ERROR + ); + } + protected function flushFieldsCache() { $this->sequentialSet = null; $this->sequentialSaveableSet = null; } + /** + * @deprecated 4.1..5.0 Please use dataFields or saveableFields + * @param $list + * @param bool $saveableOnly + */ protected function collateDataFields(&$list, $saveableOnly = false) { + Deprecation::notice('5.0', 'Please use dataFields or SaveableFields'); if (!isset($list)) { $list = array(); } diff --git a/tests/php/Forms/FieldListTest.php b/tests/php/Forms/FieldListTest.php index 9300b35dc..b6beffeec 100644 --- a/tests/php/Forms/FieldListTest.php +++ b/tests/php/Forms/FieldListTest.php @@ -3,7 +3,10 @@ namespace SilverStripe\Forms\Tests; use SilverStripe\Dev\SapphireTest; +use SilverStripe\Forms\ConfirmedPasswordField; use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\FormField; +use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\Tab; use SilverStripe\Forms\TextField; use SilverStripe\Forms\EmailField; @@ -32,6 +35,90 @@ use SilverStripe\Forms\HiddenField; */ class FieldListTest extends SapphireTest { + public function testRecursiveWalk() + { + $fields = array( + new TextField('Name'), + new EmailField('Email'), + new HiddenField('Hidden'), + new LiteralField('Literal', 'Literal content'), + new CompositeField( + new TextField('Day'), + new TextField('Month'), + new TextField('Year') + ), + ); + $fieldList = new FieldList($fields); + + $count = 0; + + $fieldList->recursiveWalk(function (FormField $field) use (&$count) { + ++$count; + }); + + $this->assertEquals(8, $count); + } + + public function testFlattenFields() + { + $fields = array( + new TextField('Name'), + new EmailField('Email'), + new HiddenField('Hidden'), + new LiteralField('Literal', 'Literal content'), + $composite = new CompositeField( + $day = new TextField('Day'), + $month = new TextField('Month'), + $year = new TextField('Year') + ), + ); + $fieldList = new FieldList($fields); + + array_pop($fields); + array_push($fields, $composite, $day, $month, $year); + + $this->assertEquals($fields, $fieldList->flattenFields()->toArray()); + } + + public function testSaveableFields() + { + $fields = array( + new TextField('Name'), + new EmailField('Email'), + new HiddenField('Hidden'), + new LiteralField('Literal', 'Literal content'), + new CompositeField( + $day = new TextField('Day'), + $month = new TextField('Month'), + $year = new TextField('Year') + ), + ); + $fieldList = new FieldList($fields); + + array_pop($fields); + array_pop($fields); + array_push($fields, $day, $month, $year); + + $this->assertEquals($fields, array_values($fieldList->saveableFields())); + } + + public function testFieldNames() + { + $fields = array( + new TextField('Name'), + new EmailField('Email'), + new HiddenField('Hidden'), + new LiteralField('Literal', 'Literal content'), + new CompositeField( + $day = new TextField('Day'), + $month = new TextField('Month'), + $year = new TextField('Year') + ), + ); + $fieldList = new FieldList($fields); + + $this->assertEquals(['Name', 'Email', 'Hidden', 'Day', 'Month', 'Year'], $fieldList->dataFieldNames()); + } /** * Test adding a field to a tab in a set.