Home > Mobile >  Symfony 4.4 CollectionType set default value for new input fields added by Javascript
Symfony 4.4 CollectionType set default value for new input fields added by Javascript

Time:09-27

Basic info/issue

Im using Symfony 4.4 to add contract data. The start date of a contract (year week nr.), end date (year week nr.) and contract hours. Since users could have multiple contracts active within a year, I use Symfony's CollectionType. Now when a user adds new contracts, the default values are set to year 2011 week 1. For both the start and end dates as you can see in the image below.

Issue with default value. 2011 everywhere...

What I want

I want the years (both start and end date) to be the current year we are existing in, dynamic so that next year it changes to 2022. Start date its week number should be 1, currently correct. End date its week number should be 52/53, based on leap year or not. So basicly just the last week of the year. This for each field generated.

What I tried

I checked posts like this and this but unfortunately no success. Mostly since these posts aren't ment for collectionType things in the way I have it.

What I have

My userEntityForm.php

->add('contracts', CollectionType::class, [
    'entry_type' => ContractsType::class,
    'entry_options' => ['label' => false],
    // I saw in the second link in "What I tried" that I could use entry_options, but I have no clue on how to use this in my case.
    // 'entry_options' => ['data' => 'What to put here as a default?'],
    'allow_add' => true,
    'allow_delete' => true,
    'by_reference' => false,
])

ContractsType.php What gets created each time user adds a contract.

$builder
    ->add('start_date', WeekType::class, [
        'label_attr' => [
            'class' => 'bold',
        ],
        'attr' => [
            'class' => 'input-margin',
        ],
        'input' => 'array',
        'widget' => 'choice',
    ])
    ->add('end_date', WeekType::class, [
        'label_attr' => [
            'class' => 'bold',
        ],
        'attr' => [
            'class' => 'input-margin',
        ],
        'input' => 'array',
        'widget' => 'choice',
    ])
    ->add('contract_hours', TextType::class, [
        'label_attr' => [
            'class' => 'bold',
        ],
        'attr' => [
            'class' => 'input-text input-margin',
            'placeholder' => 'Vul het aantal contract uren in',
            'min' => 0,
            'max' => 60
        ]
    ])
;

User.php Entity for saving the contracts its data. Not all fields are shown.

protected $contracts;

public function __construct()
{
    $this->contracts = new ArrayCollection();
}

public function getContracts(): Collection
{
    return $this->contracts;
}

public function addContract(Contracts $contract): void
{
    $this->contracts->add($contract);
}

public function removeContract(Contracts $contract): void
{
    $this->contracts->removeElement($contract);
}

MyController.php Inside the "new" function, for adding users (and contracts within):

$user = new Users();
$contracts = new Contracts();

// Since dec 28 always gives the last week in a year. Always for current year.
$lastWeekOfYear = new \DateTime('December 28th');

// Default value for start/end times.
$contracts->setStartDate(array('year' => (int)$lastWeekOfYear->format('Y'), 'week' => 01));
$contracts->setEndDate(array('year' => (int)$lastWeekOfYear->format('Y'), 'week' => (int)$lastWeekOfYear->format('W')));

// Add clocking times to hour entity.
$user->addContract($contracts);

// Create form.
$form = $this->createForm(UserEntityForm::class, $user);
$form->handleRequest($request);

Here I created 1 field by default, I get indexing errors otherwise... But this does give a good default. The first field indeed get the correct default values. Just all other fields generated by Javascript after this won't get the default values like above.

Javascript I generate fields based on a different input field that asks a user how many contracts have been active for the current year. So if the user fills in 10, 10 contracts are generated. If its 5 then 5 contracts etc.

// Only generate the remaining contracts. We dont want more than the user asks for.
for (let i = contracts.length; i < userEntityFormAmountOfContracts.value; i  ) {

    // Get the data-prototype from _form.html.twig.
    var startLabel = contractFields.dataset.startLabel;
    var startDateYear = contractFields.dataset.startDateYear;
    var startDateWeek = contractFields.dataset.startDateWeek;
    var endLabel = contractFields.dataset.endLabel;
    var endDateYear = contractFields.dataset.endDateYear;
    var endDateWeek = contractFields.dataset.endDateWeek;
    var hoursLabel = contractFields.dataset.hoursLabel;
    var contractHours = contractFields.dataset.hours;

    // get the current index.
    var index = contractFields.dataset.index;

    // Replace '__name__' in the prototype's HTML to instead be a number based on how many items we have.
    startLabel = startLabel.replace(/__name__/g, index);
    startDateYear = startDateYear.replace(/__name__/g, index);
    startDateWeek = startDateWeek.replace(/__name__/g, index);
    endLabel = endLabel.replace(/__name__/g, index);
    endDateYear = endDateYear.replace(/__name__/g, index);
    endDateWeek = endDateWeek.replace(/__name__/g, index);
    hoursLabel = hoursLabel.replace(/__name__/g, index);
    contractHours = contractHours.replace(/__name__/g, index);

    // Create extra elements.
    var contractsContainer = document.createElement('div');
    contractsContainer.classList.add("d-none", "class-to-count-amount-of-fields");

    var datesContainer = document.createElement('div');
    datesContainer.classList.add("d-flex");

    // Start date container. (label   input).
    var startDateContainer = document.createElement('div');
    startDateContainer.classList.add("mr-1");

    // Start date container. (label   input).
    var startDateInputContainer = document.createElement('div');
    startDateInputContainer.classList.add("input-margin");

    // End date container. (label   input).
    var endDateContainer = document.createElement('div');

    // Start date container. (label   input).
    var endDateInputContainer = document.createElement('div');
    endDateInputContainer.classList.add("input-margin");

    // Contract hours container. (label   input).
    var contractHoursContainer = document.createElement('div');

    // Add labels and field container to time container.
    startDateContainer.insertAdjacentHTML('beforeend', startLabel);
    endDateContainer.insertAdjacentHTML('beforeend', endLabel);
    contractHoursContainer.insertAdjacentHTML('beforeend', hoursLabel);

    // Add date input fields. (from prototype). insertAdjacentHTML means that html in string format will be transformed.
    startDateInputContainer.insertAdjacentHTML('beforeend', startDateYear);
    startDateInputContainer.insertAdjacentHTML('beforeend', startDateWeek);
    endDateInputContainer.insertAdjacentHTML('beforeend', endDateYear);
    endDateInputContainer.insertAdjacentHTML('beforeend', endDateWeek);
    contractHoursContainer.insertAdjacentHTML('beforeend', contractHours);

    startDateContainer.appendChild(startDateInputContainer);
    endDateContainer.appendChild(endDateInputContainer);

    // Add time containers to clocking times container.
    datesContainer.appendChild(startDateContainer);
    datesContainer.appendChild(endDateContainer);

    // Adding the input field parts.
    contractsContainer.appendChild(datesContainer);
    contractsContainer.appendChild(contractHoursContainer);

    // Append all, sticking together, elements to the container with all other sticking elements (contractsContainers).
    contractFields.appendChild(contractsContainer);

    // Increase the index with one for the next item.
    contractFields.dataset.index = parseInt(index)   1;
}

_form.html.twig

# Startdate, enddate and contract hours fields. Data sets are specific for each field since I need nice styling. #}
<div id="ContractFields" class="contracts"
    data-start-label="{{ form_label(userEntityForm.contracts.vars.prototype.start_date, 'Begintijd')|e('html_attr') }}" 
    data-start-date-year="{{ form_widget(userEntityForm.contracts.vars.prototype.start_date.year, { attr: { class: 'select-field mr-05' }})|e('html_attr') }}"
    data-start-date-week="{{ form_widget(userEntityForm.contracts.vars.prototype.start_date.week, { attr: { class: 'select-field' }})|e('html_attr') }}"
    data-end-label="{{ form_label(userEntityForm.contracts.vars.prototype.end_date, 'Eindtijd')|e('html_attr') }}"
    data-end-date-year="{{ form_widget(userEntityForm.contracts.vars.prototype.end_date.year, { attr: { class: 'select-field mr-05' }})|e('html_attr') }}"
    data-end-date-week="{{ form_widget(userEntityForm.contracts.vars.prototype.end_date.week, { attr: { class: 'select-field' }})|e('html_attr') }}"
    data-hours-label="{{ form_label(userEntityForm.contracts.vars.prototype.contract_hours, 'Contract uren')|e('html_attr') }}"
    data-hours="{{ form_widget(userEntityForm.contracts.vars.prototype.contract_hours, {'type' : 'number'})|e('html_attr') }}">
    
    {% for contract in userEntityForm.contracts %}

        {# Class to make sure it is known in JS how many contract fields have been added. #}
        <div class="d-none class-to-count-amount-of-fields">

            <div class="d-flex">

                {# Contract start date field #}
                <div class="mr-1">
                    {{ form_label(contract.start_date, 'Begintijd') }}
                    <div class="input-margin">
                        {{ form_widget(contract.start_date.year, { attr: { class: 'select-field mr-05' }}) }}
                        &nbsp;
                        {{ form_widget(contract.start_date.week, { attr: { class: 'select-field' }}) }}
                    </div>
                </div>

                {# Contract end date field. #}
                <div>
                    {{ form_label(contract.end_date, 'Eindtijd') }}

                    <div class="input-margin">
                        {{ form_widget(contract.end_date.year, { attr: { class: 'select-field mr-05' }}) }}
                        {{ form_widget(contract.end_date.week, { attr: { class: 'select-field' }}) }}
                    </div>
                </div>
            </div>

            {# Contract hours field. #}
            <div>
                {{ form_label(contract.contract_hours, 'Contract uren') }}
                {{ form_widget(contract.contract_hours, {'type' : 'number'}) }}
            </div>
        </div>

    {% endfor %}
</div>

CodePudding user response:

Okey so while writing this question I actually figured out an answer. It is also more simpler then I initialy thought. I just set a value at the time of creation with Javascript to the input fields.

Javascript

// Same for loop as in question above.
for (let i = contracts.length; i < userEntityFormAmountOfContracts.value; i  ) {

    // Element creations...

    // Set default values to input fields.
    const currentDate = new Date();
    startDateInputContainer.children[0].value = 2021;
    startDateInputContainer.children[1].value = 1;
    endDateInputContainer.children[0].value = 2021;
    endDateInputContainer.children[1].value = isISOLeapYear(currentDate.getFullYear()) ? 53 : 52;

    // ...
}

The isISOLeapYear() is a custom function.

I hope this helps others that are looking for the same.

  • Related