你好 Stackoverflow 社区!
我正在使用 AngularJS 和 jQuery 开发 Symfony 3 项目。我创建了一个集合指令来与 Symfony 表单构建器交互,以添加和删除集合字段类型的行。该指令有一个独立的范围,它设置了一个名为prototypeControl的2路绑定变量。在 twig 模板站点上,我调用prototype-control="{{ form.vars.id|camel_case }}Prototype"
以获取集合字段的唯一 ID,因此这将与单个表单中的多个集合字段一起使用。奇怪的是,如果我在原型控制属性中将变量名设置为原型控制,一切正常。添加和删除按钮适用于加载时存在的集合行,删除按钮适用于动态添加的行。我需要它来使用自定义变量名称,以便我可以使用页面控制器中的隔离范围函数。
长话短说,当我使用带有表单 ID 的变量使其唯一时,我可以从 javscript 控制台触发这些功能,但是无论动态添加什么字段,删除按钮都没有任何影响。我可以让删除按钮工作的唯一方法是将 $compile 注入指令并编译 directvie 元素。
$compile(element.contents())(scope);
尝试使用和不使用 .contents() 方法。使用这种方法,一切看起来都很好,删除对现有元素和动态添加的元素都有效,添加按钮有效,但无论出于何种原因,添加按钮上的 ng-click 似乎每次都会增加监听器。所以下次我点击按钮时,它会添加两行,然后是四行,依此类推。
我尝试在 DOM 的不同级别上进行编译,但删除按钮永远不起作用。我尝试过的示例元素是删除按钮本身、存储在本地容器变量中的 DOM、原型 HTML 本身以及 .prototype-row。这些似乎都没有受到 $compile 的影响。只有编译元素变量似乎有效。
这是指令:
($_ => {
$_.app.directive('formCollection', [
'$compile',
($compile) => ({
restrict: 'C',
require: '^form', // Grab the form controller from the parent <form> element,
scope: {
prototypeControl: '=',
},
link(scope, element, attr, form) {
// Declare prototypeControl as an object
scope.prototypeControl = {};
// Store the prototype markup in the scope (the template generated by Symfony)
scope.prototype = attr.collectionPrototype;
// Determine what the the next row id will be on add
let row = element.find('.prototype-row').last().data('row');
// Set the nextRow scope variable
if (typeof row !== 'undefined') {
// Next number in the sequence
scope.nextRow = row + 1;
}
else {
// There are no rows on page load. Setting the default to zero
scope.nextRow = 0;
}
// Add prototype row (add button)
scope.prototypeControl.add = ($event) => {
if (typeof $event !== 'undefined') {
// Prevent Default
$event.preventDefault();
}
// Get the element that will contain dynamically added prototype form rows
let container = element.find('.prototype-container');
// Replace the __name__ placeholder with the row id (typically the next number in the sequence)
let prototype = scope.prototype.replace(/__name__/g, scope.nextRow);
// Appened the prototype form row to the end of the prototype form rows container
angular.element(prototype).appendTo(container);
// Re-compiles the entire directive element and children to allow events like ng-click to fire on
// dynamically added prototype form rows
$compile(element.contents())(scope);
// Increase the nextRow scope variable
scope.nextRow++;
};
// Remove prototype row (remove button)
scope.prototypeControl.remove = ($event) => {
// Prevent Default
$event.preventDefault();
// Get the button element that was clicked
let el = angular.element($event.target);
// Get the entire prototype form row (for removal)
let prototypeRow = el.parents('.prototype-row');
// Remove the row from the dom (If orphan-removal is set to true on the model, the ORM will automatically
// delete the entity from the database)
prototypeRow.remove();
};
// Manual control to add a row (omits the $event var)
scope.prototypeControl.addRow = () => {
scope.prototypeControl.add();
};
// Manual control to remove a row by passing in the row id
scope.prototypeControl.removeRow = (row) => {
// Find the prototype form row by the row id
let el = angular.element(`.prototype-row[data-row="${row}"]`);
// If the element is found, remove it from the DOM
if (el.length) {
el.remove();
}
};
}
})]);
})(Unicorn);
这是来自服务器端的集合的树枝模板块。
{%- block collection_widget -%}
{% if prototype is defined and prototype %}
{% set prototypeVars = {} %}
{% set prototypeHtml = '<div class="prototype-row" data-row="__name__">' %}
{% set prototypeHtml = prototypeHtml ~ form_widget(prototype, prototypeVars) %}
{% if allow_delete is defined and allow_delete %}
{% set prototypeHtml = prototypeHtml ~ '<div class="input-action input-action-delete">' %}
{% set prototypeHtml = prototypeHtml ~ '<a href="#" class="btn btn-secondary btn-destructive btn-small prototype-remove" ng-click="' ~ form.vars.id|camel_case ~ 'Prototype.remove($event)" data-field="' ~ prototype.vars.id|camel_case ~ '">' ~ deleteButtonText|trans({}, translation_domain)| raw ~ '</a>' %}
{% set prototypeHtml = prototypeHtml ~ '</div>' %}
{% endif %}
{% set prototypeHtml = prototypeHtml ~ '</div>' %}
<div class="form-collection" prototype-control="{{ form.vars.id|camel_case }}Prototype" data-collection-prototype="{{ prototypeHtml|e('html') }}">
{% for field in form %}
<div class="prototype-row" data-row="{{ field.vars.name }}">
{{ form_widget(field) }}
{{ form_errors(field) }}
{% if allow_delete is defined and allow_delete %}
<div class="input-action input-action-delete">
<a href="#" class="btn btn-secondary btn-destructive btn-small prototype-remove" ng-click="{{ form.vars.id|camel_case }}Prototype.remove($event)" data-field="{{ field.vars.id|camel_case }}">{{ deleteButtonText|trans({}, translation_domain)| raw }}</a>
</div>
{% endif %}
</div>
{% endfor %}
<div class="prototype-container"></div>
{% if allow_add is defined and allow_add %}
<div class="input-action input-action-add">
<a href="#" class="btn btn-secondary btn-small" ng-click="{{ form.vars.id|camel_case }}Prototype.add($event)" data-collection="{{ form.vars.id|camel_case }}">{{ form.vars.addButtonText|trans({}, translation_domain) }}</a>
</div>
{% endif %}
{{ form_errors(form) }}
</div>
{% else %}
{{- block('form_widget') -}}
{% endif %}
{%- endblock collection_widget -%}
这是我正在测试的特定集合的实际模板。这是使用以下方法data-collection-prototype
动态添加到 DOM 的内容add()
:
<div class="prototype-row" data-row="__name__">
<div id="proposal_recipients___name__Container">
<div class="form-item form-item-contact">
<div id="proposal_recipients___name___contactContainer">
<div class="form-item form-item-first-name"><label class="control-label required"
for="proposalRecipientsNameContactFirstName">First
Name<span class="field-required">*</span></label>
<input type="text" id="proposalRecipientsNameContactFirstName"
name="proposal[recipients][__name__][contact][firstName]" required="required"
ng-model="proposalDetails.proposal.recipients[__name__]._contact.firstName"
ng-init="proposalDetails.proposal.recipients[__name__]._contact.firstName=''" class="input"/>
</div>
<div class="form-item form-item-last-name"><label class="control-label required"
for="proposalRecipientsNameContactLastName">Last
Name<span class="field-required">*</span></label>
<input type="text" id="proposalRecipientsNameContactLastName"
name="proposal[recipients][__name__][contact][lastName]" required="required"
ng-model="proposalDetails.proposal.recipients[__name__]._contact.lastName"
ng-init="proposalDetails.proposal.recipients[__name__]._contact.lastName=''" class="input"/>
</div>
<div class="form-item form-item-email"><label class="control-label required"
for="proposalRecipientsNameContactEmail">Email
Address<span class="field-required">*</span></label> <input type="email"
id="proposalRecipientsNameContactEmail"
name="proposal[recipients][__name__][contact][email]"
required="required"
ng-model="proposalDetails.proposal.recipients[__name__]._contact.email"
ng-init="proposalDetails.proposal.recipients[__name__]._contact.email=''"
class="input"/></div>
<div class="form-item form-item-phone"><label class="control-label"
for="proposalRecipientsNameContactPhone">Phone</label>
<input type="phone" id="proposalRecipientsNameContactPhone"
name="proposal[recipients][__name__][contact][phone]"
ng-model="proposalDetails.proposal.recipients[__name__]._contact.phone"
ng-init="proposalDetails.proposal.recipients[__name__]._contact.phone=''" class="input"/>
</div>
</div>
</div>
<div class="form-item form-item-company"><label class="control-label required"
for="proposalRecipientsNameCompany">Company<span
class="field-required">*</span></label>
<input type="text" id="proposalRecipientsNameCompany" name="proposal[recipients][__name__][company]"
required="required" ng-model="proposalDetails.proposal.recipients[__name__]._company"
ng-init="proposalDetails.proposal.recipients[__name__]._company=''" class="input"/>
</div>
<div class="form-item form-item-title"><label class="control-label required" for="proposalRecipientsNameTitle">Title<span
class="field-required">*</span></label>
<input type="text" id="proposalRecipientsNameTitle" name="proposal[recipients][__name__][title]"
required="required" ng-model="proposalDetails.proposal.recipients[__name__]._title"
ng-init="proposalDetails.proposal.recipients[__name__]._title=''" class="input"/>
</div>
<div class="form-item form-item-role"><label class="control-label required" for="proposalRecipientsNameRole">Role<span
class="field-required">*</span></label>
<select id="proposalRecipientsNameRole" name="proposal[recipients][__name__][role]" required="required"
ng-model="proposalDetails.proposal.recipients[__name__]._role"
ng-init="proposalDetails.proposal.recipients[__name__]._role=''" class="hide-search"
data-show-search="0" chosen="chosen" data-allow-single-deselect="true" data-placeholder="Select"
tabindex="-1">
<option value="" selected="selected">Select</option>
<option value="ROLE_PROPOSAL_SIGNER">Signer</option>
<option value="ROLE_PROPOSAL_READER">Reader</option>
</select>
</div>
</div>
<div class="input-action input-action-delete"><a href="#"
class="btn btn-secondary btn-destructive btn-small prototype-remove"
ng-click="proposalRecipientsPrototype.remove($event)"
data-field="proposalRecipientsName">Remove Recipient</a></div>
</div>
我仍然会通过这个来寻找答案。如果我弄清楚了,我会在这里发帖。
希望外面有人遇到这种情况一次或两次。
谢谢!