Skip to content

Instantly share code, notes, and snippets.

@quentint
Last active July 25, 2022 13:30
Show Gist options
  • Save quentint/a6959da196bfcb1fd18ba8b075ed96fa to your computer and use it in GitHub Desktop.
Save quentint/a6959da196bfcb1fd18ba8b075ed96fa to your computer and use it in GitHub Desktop.
EasyAdmin EnumField
<?php
namespace App\Form\Field\Configurator;
use App\Form\Field\EnumField;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldConfiguratorInterface;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use EasyCorp\Bundle\EasyAdminBundle\Translation\TranslatableChoiceMessage;
use EasyCorp\Bundle\EasyAdminBundle\Translation\TranslatableChoiceMessageCollection;
use function Symfony\Component\String\u;
use function Symfony\Component\Translation\t;
final class EnumConfigurator implements FieldConfiguratorInterface
{
public function supports(FieldDto $field, EntityDto $entityDto): bool
{
return EnumField::class === $field->getFieldFqcn();
}
public function configure(FieldDto $field, EntityDto $entityDto, AdminContext $context): void
{
$isExpanded = $field->getCustomOption(EnumField::OPTION_RENDER_EXPANDED);
$isMultipleChoice = $field->getCustomOption(EnumField::OPTION_ALLOW_MULTIPLE_CHOICES);
$field->setFormTypeOption('class', $field->getCustomOption(EnumField::OPTION_ENUM_CLASS));
$choices = call_user_func([$field->getCustomOption(EnumField::OPTION_ENUM_CLASS), 'cases']);
$field->setFormTypeOptionIfNotSet('choices', $choices);
$field->setFormTypeOptionIfNotSet('multiple', $isMultipleChoice);
$field->setFormTypeOptionIfNotSet('expanded', $isExpanded);
if ($isExpanded && EnumField::WIDGET_AUTOCOMPLETE === $field->getCustomOption(EnumField::OPTION_WIDGET)) {
throw new \InvalidArgumentException(sprintf('The "%s" choice field wants to be displayed as an autocomplete widget and as an expanded list of choices at the same time, which is not possible. Use the renderExpanded() and renderAsNativeWidget() methods to change one of those options.', $field->getProperty()));
}
if (null === $field->getCustomOption(EnumField::OPTION_WIDGET)) {
$field->setCustomOption(EnumField::OPTION_WIDGET, $isExpanded ? EnumField::WIDGET_NATIVE : EnumField::WIDGET_AUTOCOMPLETE);
}
if (EnumField::WIDGET_AUTOCOMPLETE === $field->getCustomOption(EnumField::OPTION_WIDGET)) {
$field->setFormTypeOption('attr.data-ea-widget', 'ea-autocomplete');
$field->setDefaultColumns($isMultipleChoice ? 'col-md-8 col-xxl-6' : 'col-md-6 col-xxl-5');
}
$field->setFormTypeOptionIfNotSet('placeholder', '');
// the value of this form option must be a string to properly propagate it as an HTML attribute value
$field->setFormTypeOption('attr.data-ea-autocomplete-render-items-as-html', $field->getCustomOption(EnumField::OPTION_ESCAPE_HTML_CONTENTS) ? 'false' : 'true');
$fieldValue = $field->getValue();
$isIndexOrDetail = \in_array($context->getCrud()->getCurrentPage(), [Crud::PAGE_INDEX, Crud::PAGE_DETAIL], true);
if (null === $fieldValue || !$isIndexOrDetail) {
return;
}
$badgeSelector = $field->getCustomOption(EnumField::OPTION_RENDER_AS_BADGES);
$isRenderedAsBadge = null !== $badgeSelector && false !== $badgeSelector;
$translationParameters = $context->getI18n()->getTranslationParameters();
$translationDomain = $context->getI18n()->getTranslationDomain();
$field->setFormattedValue(
new TranslatableChoiceMessageCollection([
new TranslatableChoiceMessage(
t(
$fieldValue->value,
$translationParameters,
$translationDomain
),
$isRenderedAsBadge ? $this->getBadgeCssClass($badgeSelector, $fieldValue->value, $field) : null
),
], $isRenderedAsBadge)
);
}
private function getBadgeCssClass($badgeSelector, $value, FieldDto $field): string
{
$commonBadgeCssClass = 'badge';
if (true === $badgeSelector) {
$badgeType = 'badge-secondary';
} elseif (\is_array($badgeSelector)) {
$badgeType = $badgeSelector[$value] ?? 'badge-secondary';
} elseif (\is_callable($badgeSelector)) {
$badgeType = $badgeSelector($value, $field);
if (!\in_array($badgeType, EnumField::VALID_BADGE_TYPES, true)) {
throw new \RuntimeException(sprintf('The value returned by the callable passed to the "renderAsBadges()" method must be one of the following valid badge types: "%s" ("%s" given).', implode(', ', EnumField::VALID_BADGE_TYPES), $badgeType));
}
}
$badgeTypeCssClass = empty($badgeType) ? '' : u($badgeType)->ensureStart('badge-')->toString();
return $commonBadgeCssClass.' '.$badgeTypeCssClass;
}
}
<?php
namespace App\Form\Field;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use Symfony\Component\Form\Extension\Core\Type\EnumType;
use Symfony\Contracts\Translation\TranslatableInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
class EnumField implements FieldInterface
{
use FieldTrait;
public const OPTION_ENUM_CLASS = 'enumClass';
public const OPTION_ALLOW_MULTIPLE_CHOICES = 'allowMultipleChoices';
public const OPTION_AUTOCOMPLETE = 'autocomplete';
public const OPTION_RENDER_AS_BADGES = 'renderAsBadges';
public const OPTION_RENDER_EXPANDED = 'renderExpanded';
public const OPTION_WIDGET = 'widget';
public const OPTION_ESCAPE_HTML_CONTENTS = 'escapeHtml';
public const VALID_BADGE_TYPES = ['success', 'warning', 'danger', 'info', 'primary', 'secondary', 'light', 'dark'];
public const WIDGET_AUTOCOMPLETE = 'autocomplete';
public const WIDGET_NATIVE = 'native';
/**
* @param TranslatableInterface|string|false|null $label
*/
public static function new(string $propertyName, $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplateName('crud/field/choice')
->setFormType(EnumType::class)
->addCssClass('field-select')
->setDefaultColumns('') // this is set dynamically in the field configurator
->setCustomOption(self::OPTION_ALLOW_MULTIPLE_CHOICES, false)
->setCustomOption(self::OPTION_RENDER_AS_BADGES, null)
->setCustomOption(self::OPTION_RENDER_EXPANDED, false)
->setCustomOption(self::OPTION_WIDGET, null)
->setCustomOption(self::OPTION_ESCAPE_HTML_CONTENTS, true);
}
public function setEnumClass(string $class): self {
$this->setCustomOption(self::OPTION_ENUM_CLASS, $class);
return $this;
}
public function allowMultipleChoices(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_MULTIPLE_CHOICES, $allow);
return $this;
}
public function autocomplete(): self
{
$this->setCustomOption(self::OPTION_AUTOCOMPLETE, true);
return $this;
}
/**
* Possible values of $badgeSelector:
* * true: all values are displayed as 'secondary' badges
* * false: no badges are displayed; values are displayed as regular text
* * array: [$fieldValue => $badgeType, ...] (e.g. ['foo' => 'primary', 7 => 'warning', 'cancelled' => 'danger'])
* * callable: function(FieldDto $field): string { return '...' }
* (e.g. function(FieldDto $field) { return $field->getValue() < 10 ? 'warning' : 'primary'; }).
*
* Possible badge types: 'success', 'warning', 'danger', 'info', 'primary', 'secondary', 'light', 'dark'
*/
public function renderAsBadges($badgeSelector = true): self
{
if (!\is_bool($badgeSelector) && !\is_array($badgeSelector) && !\is_callable($badgeSelector)) {
throw new \InvalidArgumentException(sprintf('The argument of the "%s" method must be a boolean, an array or a closure ("%s" given).', __METHOD__, \gettype($badgeSelector)));
}
if (\is_array($badgeSelector)) {
foreach ($badgeSelector as $badgeType) {
if (!\in_array($badgeType, self::VALID_BADGE_TYPES, true)) {
throw new \InvalidArgumentException(sprintf('The values of the array passed to the "%s" method must be one of the following valid badge types: "%s" ("%s" given).', __METHOD__, implode(', ', self::VALID_BADGE_TYPES), $badgeType));
}
}
}
$this->setCustomOption(self::OPTION_RENDER_AS_BADGES, $badgeSelector);
return $this;
}
public function renderAsNativeWidget(bool $asNative = true): self
{
$this->setCustomOption(self::OPTION_WIDGET, $asNative ? self::WIDGET_NATIVE : self::WIDGET_AUTOCOMPLETE);
return $this;
}
public function renderExpanded(bool $expanded = true): self
{
$this->setCustomOption(self::OPTION_RENDER_EXPANDED, $expanded);
return $this;
}
public function escapeHtml(bool $escape = true): self
{
$this->setCustomOption(self::OPTION_ESCAPE_HTML_CONTENTS, $escape);
return $this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment