EasyAdminBundle icon indicating copy to clipboard operation
EasyAdminBundle copied to clipboard

Missing Javascript functionality for TextEditorField and CollectionField on nested forms

Open Montintin opened this issue 1 year ago • 7 comments

PHP 8.3.1 Symfony 6.4.3 EasyAdmin 4.8.12

I have 3 Crud Controllers ContratosTemplates Contratos ContratosCampos

ContratosTemplates has an AssociationField that contains a Contratos Contratos has a TextEditorField and a CollectionField of ContratosCampos

When I add/edit a ContratosTemplates the TextEditorField from Contratos is not rendered and the CollectionField renders poorly

Screenshot 2024-02-01 at 6 56 56 p m

However if I add/edit a Contrato the TextEditorField displays fine and the CollectionField renders fine (collapsible header, remove button and propper field spacing)

Screenshot 2024-02-01 at 6 56 07 p m

I expected ContratosTemplates add/edit to work the same way and so far I haven't been able to find a way to activate this functionality

Here are the ConfigureFields for each CrudController

ContratosTemplatesCrudController.php

public function configureFields(string $pageName): iterable
    {
        return [
            IdField::new('id')->hideOnForm(),
            BooleanField::new('principal'),
            AssociationField::new('idContratos', 'Contrato')
                ->renderAsEmbeddedForm(
                    ContratosCrudController::class,
                    'create_contratos_inside_a_contratosTemplates',
                    'edit_contratos_inside_a_contratosTemplates'
                )
        ];
    }

ContratosCrudController.php

public function configureFields(string $pageName): iterable
    {
        return [
            IdField::new('id')->hideOnForm(),
            BooleanField::new('active'),
            TextEditorField::new('contrato'),
            CollectionField::new('contratosCampos')
                ->renderExpanded()
                ->setEntryIsComplex()
                ->useEntryCrudForm(ContratosCamposCrudController::class),
        ];
}

ContratosCamposController.php

public function configureFields(string $pageName): iterable
    {
        return [
            IdField::new('id')->hideOnForm(),
            TextField::new('label', 'Nombre de Campo'),
            TextField::new('placeholder', 'Texto de Ayuda'),
        ];
    }

Montintin avatar Feb 02 '24 01:02 Montintin

I found that if I merge the assets from a TextEditorField on the ContratosTemplatesCrudController.php, the TextEditor renders fine, no luck with the CollectionField though, also I'm not sure that's the best way to go

protected function getFieldAssets(FieldCollection $fieldDtos): AssetsDto
    {
        $fieldAssetsDto = new AssetsDto();
        $currentPageName = $this->getContext()?->getCrud()?->getCurrentPage();
        foreach ($fieldDtos as $fieldDto) {
            $fieldAssetsDto = $fieldAssetsDto->mergeWith($fieldDto->getAssets()->loadedOn($currentPageName));
        }

        // add assets
        $textEditorField = TextEditorField::new('cualquiercosa');
        $fieldAssetsDto = $fieldAssetsDto->mergeWith($textEditorField->getAsDto()->getAssets()->loadedOn($currentPageName));
       // doesn't do anything
        $collectionField = CollectionField::new('contratosCampos');
        $fieldAssetsDto = $fieldAssetsDto->mergeWith($collectionField->getAsDto()->getAssets()->loadedOn($currentPageName));

        return $fieldAssetsDto;
    }

Screenshot 2024-02-01 at 7 06 36 p m

Montintin avatar Feb 02 '24 01:02 Montintin

I found a workaround for these problems

First for the TextEditor I ended up using this so the field would render correctly

protected function getFieldAssets(FieldCollection $fieldDtos): AssetsDto
    {
        $fieldAssetsDto = new AssetsDto();
        $currentPageName = $this->getContext()?->getCrud()?->getCurrentPage();
        foreach ($fieldDtos as $fieldDto) {
            $fieldAssetsDto = $fieldAssetsDto->mergeWith($fieldDto->getAssets()->loadedOn($currentPageName));
        }

        // Add TextEditor CSS and JS
        $dto= new FieldDto();
        $dto->addJsAsset(Asset::fromEasyAdminAssetPackage('field-text-editor.js')->getAsDto());
        $dto->addCssAsset(Asset::fromEasyAdminAssetPackage('field-text-editor.css')->getAsDto());
        $fieldAssetsDto = $fieldAssetsDto->mergeWith($dto->getAssets()->loadedOn($currentPageName));

        return $fieldAssetsDto;
    }

For the CollectionField, I realized that if I used a Symfony Form instead of a an EasyAdmin CRUD Form it worked.

ContratosCrudController.php

public function configureFields(string $pageName): iterable
    {
        return [
            IdField::new('id')->hideOnForm(),
            BooleanField::new('active'),
            TextEditorField::new('contrato'),
            CollectionField::new('contratosCampos')
                ->renderExpanded()
                ->setEntryIsComplex()
               // this works
                ->setEntryType(ContratosCamposType::class)
               // this doesn't
               // ->useEntryCrudForm()
               // this also doesn't work
                // ->useEntryCrudForm(ContratosCamposCrudController::class),
        ];
}

Montintin avatar Feb 04 '24 20:02 Montintin

@javiereguiluz can this bug be fixed in the next release, this is a common use case which is somehow blocking for now :'( Thank you :D

Geolim4 avatar May 13 '24 21:05 Geolim4

Temporary fix:

Add this code where you're calling the nested form:

                    ->addJsFiles(Asset::fromEasyAdminAssetPackage('field-text-editor.js')->onlyOnForms())
                    ->addCssFiles(Asset::fromEasyAdminAssetPackage('field-text-editor.css')->onlyOnForms())

image

Geolim4 avatar May 26 '24 21:05 Geolim4

@Geolim4 Thanks for your workaround.

I had a similar problem with Collection Field and solved it like follow:

Symfony version: 6.4.7 Php version: 8.3 EasyAdmin: ^4.0

  • Nested forms worked well in EasyAdmin CRUD::PAGE_NEW create mode
  • Nested forms did not work in CRUD::PAGE_EDIT update mode

Here there is my CollectionField code:

$fields[] = CollectionField::new('categories', ucfirst($this->translator->trans('guide.prop.categories')))
                ->renderExpanded()
                ->setEntryIsComplex(true)
                ->setEntryType(GuideDraftCategoryFormType::class)
                ->setRequired(true)
;
  • Create a custom Service reading into manifest.json file:
class AssetManifestService
{
    private $manifestPath;

    private $manifestData;

    public function __construct(ParameterBagInterface $params)
    {
        $this->manifestPath = $params->get('kernel.project_dir') . '/public/bundles/easyadmin/manifest.json';
        $this->loadManifest();
    }

    #[Returns('void')]
    private function loadManifest(): void
    {
       if (file_exists($this->manifestPath)) {
           $jsonContent = file_get_contents($this->manifestPath);
           $this->manifestData = json_decode($jsonContent, true);

           if (json_last_error() !== JSON_ERROR_NONE) {
               throw new Exception('Error decoding JSON: ' . json_last_error_msg());
           }
       } else {
           throw new Exception('Manifest file not found.');
       }
    }

    #[Returns('string|null')]
    public function getAsset(string $assetName): ?string
    {
        return $this->manifestData[$assetName] ?? null;
    }
}
  • Call service into a custom EasyAdmin 4 CRUD configure assets method:
    /**
     * Add custom assets
     */
    #[Param(assets: 'Assets')]
    #[Returns('Assets')]
    public function configureAssets(Assets $assets): Assets
    {
        $jsFile = $this->assetManifestService->getAsset('field-collection.js');
        $cssFile = $this->assetManifestService->getAsset('field-text-editor.css');

      // Add here other $assets if you need

        if ($jsFile) {
            $assets->addJsFile('/bundles/easyadmin/' . $jsFile);
        }

        if ($cssFile) {
            $assets->addCssFile('/bundles/easyadmin/' . $cssFile);
        }

        return $assets;
    }

The logic above retrieve paths and file names after webpack compile from manifest.json file, then apply js and css file contents into EasyAdmin 4 back-office entities.

Maybe this is a just a temporary workaround, but collection field work well also in edit environement and solution is stable.

MirkoV1987 avatar Jun 06 '24 13:06 MirkoV1987

Thank you for the workaround. Very annoying bug...

DaanBiesterbos avatar Jun 08 '24 15:06 DaanBiesterbos

->addJsFiles(Asset::fromEasyAdminAssetPackage('field-text-editor.js')->onlyOnForms())
                    ->addCssFiles(Asset::fromEasyAdminAssetPackage('field-text-editor.css')->onlyOnForms())

Thanks to your reply among others, I was able to fix this. But in a cleaner way. This is how I fixed the CollectionField.

        yield CollectionField::new('sections')
            // This still won't work:
            //->useEntryCrudForm(DocumentSectionCrudController::class, 'embed_new', 'embed_edit')
            ->setEntryIsComplex()
            ->setEntryType(EmbeddedDocumentSectionType::class)
            ->addAssetMapperEntries(
                Asset::fromEasyAdminAssetPackage('field-text-editor.js'),
                Asset::fromEasyAdminAssetPackage('field-text-editor.css')
            )
            ->renderExpanded();

And the form:

 $builder
            ->add('title', TextType::class)
            ->add('content', TextEditorType::class)
        ;

I did not need the additional service.

DaanBiesterbos avatar Jun 08 '24 16:06 DaanBiesterbos