Zend Framework 3 - Add and Remove new input element section using javascript

▼魔方 西西 提交于 2019-12-01 21:55:25

What you're looking for is the usage of Collections (which you linked) and Fieldsets.

You use a Fieldset to represent an Entity. In this example the Fieldset is Location, attached to School.

Also, the School as a One To Many relation with Location.

As such, you would have a SchoolFieldset class, which needs a Collection Element.

Below a very simplified example of the setup.

Backend

LocationFieldset

class LocationFieldset
{
    public function init()
    {
        parent::init();

        $this->add([
            'name' => 'name',
            'required' => true,
            'type' => Text::class,
            'options' => [
                'label' => _('Name'),
            ],
        ]);

        // ... Add whatever for Location
    }
}

SchoolFieldset

class SchoolFieldset
{
    /**
     * @var LocationFieldset
     */
    protected $locationFieldset;

    public function __construct(LocationFieldset $locationFieldset) 
    {
        $this->locationFieldset($locationFieldset);
    }

    public function init()
    {
        parent::init();

        $this->add([
            'name' => 'name',
            'required' => true,
            'type' => Text::class,
            'options' => [
                'label' => _('Name'),
            ],
        ]);

        $this->add([
            'type' => Collection::class,
            'required' => true,
            'name' => 'locations',
            'options' => [
                'label' => _('Locations'),
                'count' => 1,                     // Initial amount of Fieldsets on-load
                'allow_add' => true,              // Allows creation of 0/multiple
                'allow_remove' => true,           // Allows removal
                'should_create_template' => true, // Creates template in the HTML in a <span data-template="the whole html here"></span> -> JavaScript this bit for duplication/removal
                'target_element' => $this->locationFieldset, // A pre-loaded Fieldset must be passed here, not just the FQCN as you would for including a Fieldset not in a Collection
            ],
        ]);

        // ... Add whatever
    }
}

SchoolForm

class SchoolForm extends CustomAbstractForm
{
    public function init()
    {
        $this->add([
            'name' => 'school',
            'type' => SchoolFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);

        //Call parent initializer. (Default for me it adds a submit button)
        parent::init();
    }
}

Front-end

In the view of the form, I load a bit of JavaScript. It's based on the demo data given in the Zend Framework documentation.

Please note that those docs do not account for removal (so if you have HTML objects with id's 0-1-2 and you remove 1, it will count, come to 2 and create another 2, giving you 0-2-2 and thus an overwrite for the second one you already had).

The JavaScript I have in a project at the moment is this (sorry, cannot give you all of it, but this should get you started):

Buttons

var $addButton = $('<button type="button" data-action="add-fieldset" class="btn btn-primary">Add another</button>');
var $removeButton = $('<button type="button" data-action="remove-fieldset" class="btn btn-danger">Remove</button>');

Usage

$('body').on('click', 'button[type="button"][data-action="add-fieldset"]', function () {
    addCollectionFieldset(this);
});

$('body').on('click', 'button[type="button"][data-action="remove-fieldset"]', function () {
    removeCollectionFieldset(this);
});

function addCollectionFieldset(element) {
    var $element = $(element);
    var $fieldsetDataSpan = $element.siblings('span[data-name="fieldset-data"]');
    var fieldsetCount = $fieldsetDataSpan.data('fieldset-count');

    var escapedTemplate = $element.siblings('span[data-template]').data('template');
    var $replaced = $(escapedTemplate.replace(/__index__/g, fieldsetCount));
    $replaced.append($removeButton.clone());

    $($replaced).insertAfter($element.siblings('fieldset:last'));
    $('<hr>').insertBefore($element.siblings('fieldset:last'));

    $fieldsetDataSpan.data('fieldset-count', fieldsetCount + 1); // Up the count by one fieldset
}

function removeCollectionFieldset(element) {
    $(element).parent().remove();
}

Note: the "Remove" button is placed IN every Fieldset within the Collection. The "Add another" button is placed below the Collection.

How you solve that issue, is up to you.

View

<?= $this->form($form) ?>
<?php $this->inlineScript()->prependFile($this->basePath('js/form.js')) ?>

Controller action

public function addAction()
{
    /** @var SchoolForm $form */
    $form = $this->getSchoolForm();

    /** @var Request $request */
    $request = $this->getRequest();
    if ($request->isPost()) {
        $form->setData($request->getPost());

        if ($form->isValid()) {
            /** @var School $school */
            $school = $form->getObject();

            $this->getObjectManager()->persist($school);

            try {
                $this->getObjectManager()->flush();
            } catch (Exception $e) {

                throw new Exception(
                    'Could not save. Error was thrown, details: ' . $e->getMessage(),
                    $e->getCode(),
                    $e->getPrevious()
                );
            }

            return $this->redirectToRoute('schools/view', ['id' => $school->getId()]);
        }
    }

    return [
        'form' => $form,
        'validationMessages' => $form->getMessages() ?: '',
    ];
}

ControllerFactory

class AddControllerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        /** @var ObjectManager $objectManager */
        $objectManager = $container->get(EntityManager::class);

        /** @var FormElementManagerV3Polyfill $formElementManager */
        $formElementManager = $container->get('FormElementManager');
        /** @var SchoolForm $schoolForm */
        $schoolForm = $formElementManager->get(SchoolForm::class);

        return new AddController($objectManager, $schoolForm);
    }
}

FormFactory

class SchoolFormFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $objectManager = $container->get(EntityManager::class);
        $translator = $container->get('MvcTranslator');
        $inputFilterPluginManager = $container->get('InputFilterManager');

        $inputFilter = $inputFilterPluginManager->get(SchoolFormInputFilter::class); // Did not show this one

        /** @var SchoolForm $form */
        $form = new SchoolForm();
        $form->setObjectManager($objectManager);
        $form->setTranslator($translator);
        $form->setInputFilter($inputFilter);

        return $form;
    }
}

FormFactory

class SchoolFieldsetFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {        
        $objectManager = $container->get(EntityManager::class);
        $translator = $container->get('MvcTranslator');

        $fieldset = new SchoolFieldset();
        $fieldsetObject = new School();

        /** @var SchoolFieldset $fieldset */
        $fieldset = new $fieldset($objectManager(), 'school');
        $fieldset->setHydrator(
            new DoctrineObject($objectManager())
        );
        $fieldset->setObject($fieldsetObject);
        $fieldset->setTranslator($translator);

        return $fieldset;
    }
}

If you have a few moments to spare, I would advise you to check out more examples in a repo I created to help quickly create forms in ZF and ZF with Doctrine. The ReadMe with examples is here

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!