Symfony 4.4如何使用collectionType从0字段开始



我的表单中有一个基于实体的collectionType。我希望用户能够根据不同的字段动态添加新字段。因此,在页面加载时,我希望显示0个字段。在用户填写所有字段以使生成过程成为可能并且用户点击";下一个";按钮,则应添加新的集合字段。

目前,符号形式总是生成1个字段。但由于我还没有给它任何数据,它只显示了一个空的div。但它破坏了我的Javascript:(

我知道我可以为此做一个变通方法,但我希望这个字段根本不生成。在用户点击"用户界面"之前不应该制作html内容;下一个按钮";。

这是我收集的部分表格:

// Builder ...
->add('contracts', CollectionType::class, [
'entry_type' => ContractsType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
])

这是合同类型:

->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
]
])

我的小树枝:

{# 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-1' }})|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-1' }})|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') }}">

// This if is here for testing, doesn't do much about the generating part.
{% if userEntityForm.contracts is empty %}
{# {{ form_widget(userEntityForm.contracts) }} #}
{% else %}
{% 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-2">
{{ form_label(contract.start_date, 'Begintijd') }}
<div class="input-margin">
{{- form_widget(contract.start_date.year, { attr: { class: 'select-field mr-1' }}) -}}
{{- 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-1' }}) -}}
{{- 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 %}
{% endif %}
</div>

我的Javascript:

// 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-2");
// 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);
// Set default values.
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;
// Increase the index with one for the next item.
contractFields.dataset.index = parseInt(index) + 1;
}

如果您看到整个集合的标签(在您的情况下可能应为"Contracts"),您可以通过将'label' => false添加到父窗体来删除该标签,如下所示:

// Builder ...
->add('contracts', CollectionType::class, [
'label' => false,
...
])

调用form_end(form)时,Symfony会检查是否有忘记手动渲染的字段。它通过在每个字段(FormView)上调用isRendered方法,然后将缺少的字段添加到表单的末尾。对于缺少的集合,它会添加(取决于表单主题)一个带有<label><div>和一个容器<div>,该容器包含子表单(如果有的话)。

如果集合作为一个整体(form_row(collection)form_widget(collection))或其所有子形式(form_row(child)form_widget(child))都已呈现,则isRendered方法返回true。在使用循环单独呈现子窗体,但还没有子窗体的特殊情况下,isRendered返回false,并将额外的字段添加到窗体的末尾。

上述解决方案仅从表单中删除<label>

更好的解决方案可能是将{'render_rest': false}添加到form_end(form)标签中,以禁用所有附加字段的呈现:{{ form_end(form, {'render_rest': false}) }}

编辑:当调用form_end(form)时,Symfony也会渲染crsf字段,所以当使用{'render_rest': false}时,需要使用{{ form_widget(form._token) }}手动渲染。

最新更新