Skip to content

Commit

Permalink
ENHANCEMENT Add flattenFields() function to iterate over all deeply n…
Browse files Browse the repository at this point in the history
…ested fields in a form
  • Loading branch information
silbinarywolf authored and dhensby committed Dec 14, 2017
1 parent 7df3c49 commit da9c133
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 4 deletions.
107 changes: 103 additions & 4 deletions src/Forms/FieldList.php
Expand Up @@ -58,6 +58,38 @@ public function __clone()
}
}

/**
* 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.
Expand All @@ -66,8 +98,19 @@ public function __clone()
*/
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;
}
Expand All @@ -77,20 +120,76 @@ public function dataFields()
*/
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();
}
Expand Down
87 changes: 87 additions & 0 deletions tests/php/Forms/FieldListTest.php
Expand Up @@ -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;
Expand Down Expand Up @@ -32,6 +35,90 @@
*/
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.
Expand Down

0 comments on commit da9c133

Please sign in to comment.