formCollection with templated elements?

Im trying to figure out how do i implement this scenario:
I’ve got a form and i need to display a collection and user can dinamically add elements to that collection, formCollection with shouldCreateTemplate seems perfect for the job until i need to assign a custom style template to those elements.

Im looking how to use View Helpers but im stuck, any hints/solutions?

I also tried to partialLoop the collection to a template.phtml, i can perfectly render them but i lose the “shouldCreateTemplate” function

Hello and welcome to our forums! :smiley:

There are many options. The view helper formCollection adds a fieldset, a legend and the template but everything else is rendered by the formRow helper – if nothing else has been set.

Can you give an example which part should get a custom style?

here some code:

Form Class
<?php 
namespace Form\Model\Entity;

use Laminas\Form\Annotation; //annotation is for fun

/**
 * @Annotation\Name("form")
 */
class Form {

    //[...]
    
    /**
     * @Annotation\Name("child")
     * @Annotation\Type("Form\Model\Entity\Privato\Coll")
     */
    private $child_coll;
}
?>
Collection Class
<?php
namespace Form\Model\Entity\Privato;

use Laminas\Form\Element;

class Coll extends \Laminas\Form\Fieldset implements \Laminas\InputFilter\InputFilterProviderInterface{

    public function __construct() {
        parent::__construct();
		
        $this->add([
            'type' => Element\Collection::class,
            'options' => [
                'count' => 1, //test_c
                'allow_add' => true,
                'should_create_template' => true,
                'template_placeholder' => '__placeholder__',
                'target_element' => [
                    'type' => CollElemFieldset::class,
                ],
            ],
        ]);
    }

    public function getInputFilterSpecification(): array {
        return [];
    }

}
Collection Element Class
<?php

namespace Form\Model\Entity\Privato;


use Laminas\Form\Element;

class CollElemFieldset extends \Laminas\Form\Fieldset implements \Laminas\InputFilter\InputFilterProviderInterface {
    
    public function __construct() {
        parent::__construct('child_elem');
        
        $this->add([
            'name' => 'surname',
            'type' => Element\Text::class,
            'options' => [
                'label' => 'Cognome',
            ],
            'attributes' => [
                'required' => 'required',
            ],
        ]);
        $this->add([
            'name' => 'name',
            'type' => Element\Text::class,
            'options' => [
                'label' => 'Nome',
            ],
            'attributes' => [
                'required' => 'required',
            ],
        ]);
        
        $this->add([
            'name' => 'gender',
            'type' => \Form\Element\GenderSelect::class,
            'options' => [
                'label' => 'Sesso',
            ],
            'attributes' => [
                'required' => 'required',
            ],
        ]);
        
        $this->add([
            'name' => 'maritial_status',
            'type' => \Form\Element\MaritialStatus::class,
            'options' => [
                'label' => 'Stato civile',
            ],
            'attributes' => [
                'required' => 'required',
            ],
        ]);
        
        $this->add([
            'name' => 'birthday',
            'type' => Element\Date::class,
            'options' => [
                'label' => 'Data di nascita',
            ],
            'attributes' => [
                'required' => 'required',
            ],
        ]);

        $this->add([
            'name' => 'npwp',
            'type' => Element\Text::class,
            'options' => [
                'label' => 'Codice fiscale',
            ],
        ]);
    }
    
    public function getInputFilterSpecification(): array {
        return [];
    }
}
child_elem.phtml
<?php
$reg = $this->reg;
$formLabel = $this->plugin('formLabel');

$surname = $reg->get('surname');
$surname->setAttribute('class', 'form-control custom_surname');

$name = $reg->get('name');
$name->setAttribute('class', 'form-control');

$gender = $reg->get('gender');
$gender->setAttribute('class', 'form-control');

$maritial_status = $reg->get('maritial_status');
$maritial_status->setAttribute('class', 'form-control');

$birthday = $reg->get('birthday');
$birthday->setAttribute('class', 'form-control');

$npwp = $reg->get('npwp');
$npwp->setAttribute('class', 'form-control npwp');
?>

<div class="box_person">
    <div class="form-row">
        <div class="col-md-5">
            <?php
            echo $formLabel->openTag() . $surname->getOption('label');
            echo $formLabel->closeTag();
            echo $this->formInput($surname);
            echo $this->formElementErrors($surname);
            ?>
        </div>
        <div class="col-md-4">
            <?php
            echo $formLabel->openTag() . $name->getOption('label');
            echo $formLabel->closeTag();
            echo $this->formInput($name);
            echo $this->formElementErrors($name);
            ?>
        </div>
		<div class="col-md-3">
            <?php
            echo $formLabel->openTag() . $gender->getOption('label');
            echo $formLabel->closeTag();
            echo $this->formSelect($gender);
            echo $this->formElementErrors($gender);
            ?>
        </div>
    </div>

    <div class="form-row">
		
        <div class="col-md-4">
            <?php
            echo $formLabel->openTag() . $maritial_status->getOption('label');
            echo $formLabel->closeTag();
            echo $this->formSelect($maritial_status);
            echo $this->formElementErrors($maritial_status);
            ?>
        </div>
        <div class="col-md-4">
            <?php
            echo $formLabel->openTag() . $birthday->getOption('label');
            echo $formLabel->closeTag();
            echo $this->formInput($birthday);
            echo $this->formElementErrors($birthday);
            ?>
        </div>
        <div class="col-md-4">
            <?php
            echo $formLabel->openTag() . $npwp->getOption('label');
            echo $formLabel->closeTag();
            echo $this->formInput($npwp);
            echo $this->formElementErrors($npwp);
            ?>
        </div>
    </div>
</div>
index.phtml
<?php
	//...
	
	// custom render but no dynamic template
	$collection = $form->get('child')->get('collection');
	$this->partialLoop()->setObjectKey('reg');
    echo $this->partialLoop('child_elem.phtml', $collection);
	
	//not custom render but dynamic template
	echo $this->formCollection($collection);
	
	//...
?>

Should I create a custom formRow view helper and call the child_elem.phtml ?

You can set a partial script on the formRow helper for the rendering:

$this->formCollection()->setElementHelper(
    $this->formRow()->setPartial('partial.phtml')
);
echo $this->formCollection($form->get('example_collection'));

In the partial script you can use the following variables:

/**
 * @var Laminas\Form\ElementInterface $element
 * @var string|null                   $label
 * @var array|null                    $labelAttributes
 * @var string|null                   $labelPosition
 * @var bool                          $renderErrors
 *

Sorry for late reply,
im testing your solution and i suppose in that partial.phtml i can modify the style of each single element, but how do i differentiate one from another? i’ll attach the code i tried

partial.phtml
<?php
$formLabel = $this->plugin('formLabel');
$element->setAttributes(['class' => 'form-control']);
?>

<div class="col-md-4">
    <?php
    echo $formLabel->openTag() . $label;
    echo $formLabel->closeTag();
    echo $this->formInput($element);
    ?>
</div>
?>
index.phtml
//using your code
$collection = $form->get('child')->get('collection');
$this->formCollection()->setElementHelper(
     $this->formRow()->setPartial('partial.phtml')
);
echo $this->formCollection($collection);

In this case my partial.phtml contains a col-md-4 that needs to be different for almost all elements rendered. (ex. ‘surname’ needs col-md-5 ; ‘gender’ needs col-md-3 ; etc.)

If i misunderstood your point let me know
Thanks for your time

You can get the name of each element and output the related column class:

$classAttributes = [
    'surname' => 'col-md-5',
    // …
];

Another option is to skip the formCollection helper and to use a loop:

<?php
/** Laminas\Form\Element $element **/
foreach($form->get('example_collection') as $element): ?>

    <div class="<?= $classAttributes[$element->getName()] ?>">
        <?= $this->formRow($element) ?>
    </div>

<?php endforeach ?>

The template for new elements can be rendered with:

echo $this->formCollection()->renderTemplate($form->get('example_collection'));
1 Like

@MatteoF
Have you found a suitable solution?

Yeah, applying this solution and some minor edits i managed to solve my problem.

Thank you very much! :smiley: