我有一个带有单个选择字段的表单。我已将 select2 附加到它。通过 Javascript,我在客户端本身添加/删除更多此类字段。随机地,添加的字段之一失去了 select2 功能。
JS 小提琴:小提琴
重现:单击
Add more
两次。现在单击首先添加的字段(即列表中的第二个字段)的 Remove
。再次点击 Add more
。
Javascript和HTML的一些解释
字段的 id 格式为“field-x-instance”,其中 x 是字段的编号/索引。
删除字段时,我会减少删除实例后存在的所有字段的索引。为了实现此目的,我正在修改
id, for, name
等属性以在属性中使用索引-1,其中索引是正在修改的字段的当前索引。
在添加新字段时,我正在克隆一个隐藏模板字段,该字段已放入 DOM 中,id 为“field-_-instance”,然后用适当的索引替换下划线(=当前存在的字段数)。
目前已调查
根据我的调查,如果我每次为新添加的字段分配一个新索引,则不会出现该问题。为了实现这一目标,我创建了一个单调递增的计数器,并用它来修改新字段的 id/for/name。
删除字段效果很好。指标适当调整。只有当我添加新字段时,问题才会随机出现,前提是我使用当前字段数作为新表单的索引(基于 0 的索引)
代码
document.addEventListener('DOMContentLoaded', function(event) {
// Add select2 to select tags when html is first loaded
let fieldsContainer = document.getElementById('fields-container');
let selectTags = fieldsContainer.querySelectorAll('select');
for (let selectTag of selectTags) {
$(selectTag).select2();
}
// Remove functionality.
fieldsContainer.addEventListener('click', function(event) {
event.preventDefault();
if (!event.target.classList.contains('remove')) {
return false;
}
let currentElement = event.target;
let field = currentElement.closest('.field-instance');
let fieldIndex = field.dataset.index;
field.remove();
adjustIndices(fieldIndex);
});
// Add functionality
addButton = document.getElementById('add');
addButton.addEventListener('click', function(event) {
// clone template field
let templateField = document.getElementById('field-_-instance');
let newField = templateField.cloneNode(true);
// find what should be the index and update the template field with the index.
let lastField = document.getElementById('fields-container').querySelector('.field-instance:last-child');
let lastFieldIndex = parseInt(lastField.dataset.index);
updateFieldWithNewIndex(newField, /(-)_(-)/, lastFieldIndex + 1);
// insert the element in the DOM
fieldsContainer.insertAdjacentElement('beforeend', newField);
newField.classList.remove('is_hidden');
newField.classList.add('field-instance');
// Initialize select2 on this new element
let selectTags = newField.querySelectorAll('select');
for (let selectTag of selectTags) {
$(selectTag).select2();
}
});
});
// function to reduce index by 1 for all fields that are after the removed field.
function adjustIndices(removedIndex) {
let fieldInstances = document.querySelectorAll('.field-instance');
for (let fieldInstance of fieldInstances) {
let index = fieldInstance.dataset.index;
if (index < removedIndex) {
continue;
}
updateFieldWithNewIndex(fieldInstance, /(-)\d+(-)/, index - 1);
}
}
// function to replace the index with a new index
function updateFieldWithNewIndex(field, regex, newIndex) {
field.id = field.id.replace(regex, `$1${newIndex}$2`);
field.dataset.index = newIndex;
for (let elem of field.querySelectorAll('*')) {
for (let attr of ['id', 'name', 'for']) {
if (elem.hasAttribute(attr)) {
elem.setAttribute(attr, elem.getAttribute(attr).replace(/(-)_(-)/, `$1${newIndex}$2`));
}
}
}
}
.is_hidden {
display: none;
}
<html>
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet">
</head>
<body>
<h1>Hello</h1>
<form action="" method="post">
<div id="fields-container">
<div id="field-0-instance" class="field-instance" data-index="0">
<label for="field-0-phone_number">Phone number</label>
<select id="field-0-phone_number" name="field-0-phone_number" data-select2-id="field-0-phone_number" tabindex="-1" class="select2-hidden-accessible" aria-hidden="true">
<option value="" data-select2-id="2">None</option>
<option value="1">1234</option>
<option value="2">2345</option>
<option value="3">3456</option>
</select><span class="select2 select2-container select2-container--default" dir="ltr" data-select2-id="1" style="width: 60px;"><span class="selection"><span class="select2-selection select2-selection--single" role="combobox" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-disabled="false" aria-labelledby="select2-field-0-phone_number-container"><span class="select2-selection__rendered" id="select2-field-0-phone_number-container" role="textbox" aria-readonly="true" title="None">None</span>
<span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span>
</span>
</span><span class="dropdown-wrapper" aria-hidden="true"></span></span>
</div>
</div>
<a id="add" href="#">Add more</a>
</form>
<div id="field-_-instance" class="is_hidden" data-index="_">
<label for="field-_-phone_number">Phone number</label>
<select id="field-_-phone_number" name="field-_-phone_number">
<option value="">None</option>
<option value="1">1234</option>
<option value="2">2345</option>
<option value="3">3456</option>
</select>
<a class="remove" href="#">Remove</a>
</div>
</body>
</html>
不是随机的
要重新创建,请单击“添加”、“添加”、“添加”并从中间删除一个,然后再次单击“添加”。
无论如何,这是一个更简单、更干燥的重写
document.addEventListener('DOMContentLoaded', function(event) {
let fieldsContainer = document.getElementById('fields-container');
fieldsContainer.querySelectorAll('select').forEach(select => $(selectTag).select2());
const templateDiv = document.getElementById("fieldInstanceTemplate");
// Remove functionality.
fieldsContainer.addEventListener('click', function(event) {
event.preventDefault();
let currentElement = event.target.closest("a");
if (!currentElement.matches('.remove')) return false;
let field = currentElement.closest('.field-instance');
let fieldIndex = field.dataset.index;
field.remove();
});
// Add functionality
addButton = document.getElementById('add');
addButton.addEventListener('click', function(event) {
// clone template field
let newField = templateDiv.content.cloneNode(true);
// insert the element in the DOM
fieldsContainer.append(newField);
const allInstances = fieldsContainer.querySelectorAll(".field-instance")
allInstances.forEach((div, i) => {
div.querySelector(".remove").hidden = i === 0;
div.querySelectorAll("[id^='field-']").forEach(elem => {
['id', 'name', 'for'].forEach(thisattr => {
let attr = elem.getAttribute(thisattr);
if (attr) elem.setAttribute(thisattr, attr.replace(/-.*?-/, `-${i}-`)) // replace first occurrence
})
})
});
// Initialize select2 on this new element
[...allInstances].pop().querySelectorAll("select").forEach(selectTag => $(selectTag).select2());
});
addButton.click()
});
.is_hidden {
display: none;
}
<html>
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet">
</head>
<body>
<h1>Hello</h1>
<form action="" method="post">
<div id="fields-container">
</div>
<a id="add" href="#">Add more</a>
</form>
<template id="fieldInstanceTemplate">
<div class="field-instance">
<label>Phone number
<select id="field-_-phone_number" name="field-_-phone_number">
<option value="">None</option>
<option value="1">1234</option>
<option value="2">2345</option>
<option value="3">3456</option>
</select></label>
<a class="remove" href="#" hidden>Remove</a>
</div>
</template>
</body>
</html>