JSFiddle - React, Tailwind, and code Playground
HTML
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<button class="add-btn">追加</button>
<div class="item-box"></div>
<script src="src/index.js"></script>
</body>
</html>
JavaScript
document.addEventListener("DOMContentLoaded", function () {
document.querySelector(".add-btn").addEventListener("click", appendItem);
});
/** 要素を追加する関数 */
function appendItem() {
/** 追加要素につけるID */
const randomId = `id-${Math.random()}`;
/**
* 入力欄のインデックス。getNextIndex 関数内で空いている最小のインデックスを取得する。
* これをすると次の様な中抜けの補完ができる。
* [0, 1, 2] -削除-> [0, 2] -追加-> [0, 1, 2]
*/
const nextIndex = getNextIndex();
/**
* 追加する要素のHTML。ここで先程作ったIDとインデックスを使う。
* randomIdを用いることでピンポイントで要素を削除できるようにする。
* item[nextIndex][任意キー]とすることで、
* サーバー側で受け取ったリクエストでは item[index] 以下の連想配列に意味のある一セットが集まる様にする
*/
const template = `
<button type="button" class="btn-remove" onclick="document.getElementById('${randomId}').remove()">削除</button>
<label>入力欄のラベル</label>
<div class="input-box">
<input type="text" name="item[${nextIndex}][name]">
<input type="text" name="item[${nextIndex}][email]">
</div>
`;
// ↑で定義した要素を中身に持つ要素オブジェクトを作る
const el = document.createElement("div");
el.id = randomId; // 削除用ランダムID
el.dataset.index = nextIndex; // この要素がどのインデックスを持つか読み取りやすくする
el.innerHTML = template; // innerHTML で中身を丸ごと渡す。わりと高速な処理
// 出来上がった要素を入れるべき場所に追加
document.querySelector(".item-box").appendChild(el);
}
/** 追加する要素がどのインデックスを取るべきか教える関数 */
function getNextIndex() {
// 既存要素が使用中のインデックスを列挙
const usedIndex = Array.from(document.querySelectorAll(".item-box > *")).map(
(el) => +el.dataset.index
);
usedIndex.sort();
// 抜け番探し
let prev = usedIndex[0];
// 0 が存在しないならば、0が抜け番
let missingNo = prev != null && prev !== 0 ? 0 : null;
// 順々に見て行ってずれが1出なくなった場合、それが抜け番
if (missingNo === null) {
for (let current of usedIndex.slice(1)) {
if (prev + 1 !== current) {
missingNo = prev + 1;
break;
}
prev = current;
}
}
// 抜け番が見つかればそれを返す。見つからなければ現在の最大値+1を返す
return missingNo !== null ? missingNo : usedIndex.slice(-1) + 1;
}