Select2 在向 DOM 添加另一个元素时会破坏自身

问题描述 投票:0回答:1

我有一个带有单个选择字段的表单。我已将 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>

javascript html jquery select jquery-select2
1个回答
0
投票

不是随机的

要重新创建,请单击“添加”、“添加”、“添加”并从中间删除一个,然后再次单击“添加”。

无论如何,这是一个更简单、更干燥的重写

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>

© www.soinside.com 2019 - 2024. All rights reserved.