Skip to content

Commit

Permalink
Add ErrorSummary (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed Dec 4, 2023
1 parent a265ced commit b92c2f3
Show file tree
Hide file tree
Showing 4 changed files with 535 additions and 0 deletions.
230 changes: 230 additions & 0 deletions src/Field/ErrorSummary.php
@@ -0,0 +1,230 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Form\Field;

use Yiisoft\Form\Field\Base\BaseField;
use Yiisoft\Html\Html;

use function in_array;

/**
* @psalm-type Errors = array<string,list<string>>
*/
final class ErrorSummary extends BaseField
{
/**
* @psalm-var Errors
*/
private array $errors = [];
private bool $encode = true;
private bool $onlyFirst = false;
private array $onlyProperties = [];

private string $footer = '';
private array $footerAttributes = [];
private string $header = '';
private array $headerAttributes = [];
private array $listAttributes = [];

/**
* @psalm-param Errors $errors
*/
public function errors(array $errors): self
{
$new = clone $this;
$new->errors = $errors;
return $new;
}

/**
* Whether error content should be HTML-encoded.
*/
public function encode(bool $value): self
{
$new = clone $this;
$new->encode = $value;
return $new;
}

public function onlyFirst(bool $value = true): self
{
$new = clone $this;
$new->onlyFirst = $value;
return $new;
}

/**
* Specific properties to be filtered out when rendering the error summary.
*
* @param array $names The property names to be included in error summary.
*/
public function onlyProperties(string ...$names): self
{
$new = clone $this;
$new->onlyProperties = $names;
return $new;
}

/**
* Use only common errors when rendering the error summary.
*/
public function onlyCommonErrors(): self
{
$new = clone $this;
$new->onlyProperties = [''];
return $new;
}

/**
* Set the footer text for the error summary
*/
public function footer(string $value): self
{
$new = clone $this;
$new->footer = $value;
return $new;
}

/**
* Set footer attributes for the error summary.
*
* @param array $values Attribute values indexed by attribute names.
*
* See {@see Html::renderTagAttributes} for details on how attributes are being rendered.
*/
public function footerAttributes(array $values): self
{
$new = clone $this;
$new->footerAttributes = $values;
return $new;
}

/**
* Set the header text for the error summary
*/
public function header(string $value): self
{
$new = clone $this;
$new->header = $value;
return $new;
}

/**
* Set header attributes for the error summary.
*
* @param array $values Attribute values indexed by attribute names.
*
* See {@see Html::renderTagAttributes} for details on how attributes are being rendered.
*/
public function headerAttributes(array $values): self
{
$new = clone $this;
$new->headerAttributes = $values;
return $new;
}

/**
* Set errors list container attributes.
*
* @param array $attributes Attribute values indexed by attribute names.
*
* See {@see Html::renderTagAttributes} for details on how attributes are being rendered.
*/
public function listAttributes(array $attributes): self
{
$new = clone $this;
$new->listAttributes = $attributes;
return $new;
}

/**
* Add one or more CSS classes to the list container tag.
*
* @param string|null ...$class One or many CSS classes.
*/
public function addListClass(?string ...$class): self
{
$new = clone $this;
Html::addCssClass($new->listAttributes, $class);
return $new;
}

/**
* Replace current list container tag CSS classes with a new set of classes.
*
* @param string|null ...$class One or many CSS classes.
*/
public function listClass(?string ...$class): self
{
$new = clone $this;
$new->listAttributes['class'] = $class;
return $new;
}

protected function generateContent(): ?string
{
$errors = $this->filterErrors();
if (empty($errors)) {
return null;
}

$content = [];

if ($this->header !== '') {
$content[] = Html::div($this->header, $this->headerAttributes)->render();
}

$content[] = Html::ul()
->attributes($this->listAttributes)
->strings($errors, [], $this->encode)
->render();

if ($this->footer !== '') {
$content[] = Html::div($this->footer, $this->footerAttributes)->render();
}

return implode("\n", $content);
}

/**
* Return array of the validation errors.
*
* @return string[] Array of the validation errors.
*/
private function filterErrors(): array
{
if (empty($this->errors)) {
return [];
}

$errors = $this->errors;

if (!empty($this->onlyProperties)) {
$errors = array_filter(
$errors,
fn(string $property) => in_array($property, $this->onlyProperties, true),
ARRAY_FILTER_USE_KEY,
);
}

if ($this->onlyFirst) {
$errors = array_map(
static fn(array $propertyErrors) => array_slice($propertyErrors, 0, 1),
$errors,
);
}

$result = [];
foreach ($errors as $propertyErrors) {
$result = [...$result, ...$propertyErrors];
}

/**
* If there are the same error messages for different properties, `array_unique` will leave gaps between
* sequential keys.
*/
return array_unique($result);
}
}
15 changes: 15 additions & 0 deletions src/PureField.php
Expand Up @@ -13,6 +13,7 @@
use Yiisoft\Form\Field\DateTime;
use Yiisoft\Form\Field\DateTimeLocal;
use Yiisoft\Form\Field\Email;
use Yiisoft\Form\Field\ErrorSummary;
use Yiisoft\Form\Field\Fieldset;
use Yiisoft\Form\Field\File;
use Yiisoft\Form\Field\Hidden;
Expand All @@ -33,6 +34,9 @@
use Yiisoft\Form\Field\Time;
use Yiisoft\Form\Field\Url;

/**
* @psalm-import-type Errors from ErrorSummary
*/
class PureField
{
/**
Expand Down Expand Up @@ -116,6 +120,17 @@ final public static function email(
->inputData(new PureInputData($name, $value));
}

/**
* @psalm-param Errors $errors
*/
final public static function errorSummary(
array $errors = [],
array $config = [],
?string $theme = null,
): ErrorSummary {
return ErrorSummary::widget(config: $config, theme: $theme ?? static::DEFAULT_THEME)->errors($errors);
}

final public static function fieldset(array $config = [], ?string $theme = null): Fieldset
{
return Fieldset::widget(config: $config, theme: $theme ?? static::DEFAULT_THEME);
Expand Down

0 comments on commit b92c2f3

Please sign in to comment.