okashi
@okashi (oyatsu daisuki)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

JavaScriptでリストを生成し、それぞれ個別にdeleteできるようにしたい

Q&A

Closed

やりたいこと

メッセージを入力すると、リストができる。
各リストには deleteボタンがついていて、クリックすると、クリックしたリストだけ消去したい。

やろうとしたこと

リストを作ったらカウントし、そのカウントを リストに keyなどで連番をつけて、
消去できるようにしたかった。

知りたいこと

  • どうやってリストに連番を付与したらいいかわからない。
document.createElement('li');

でリストを生成するよりも、

item2.innerHTML = mess01 + '<div id="delete-bt">delete</div>';

の部分で下記のようにした方がいいのか

item2.innerHTML = '<li key=' + 番号 + mess01 + '<div id="delete-bt">delete</div>';
  • また、このやり方でいいのか、このようなことをする時に、一般的にはどんな風にされているのか
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>JS入力表示リスト</title>
</head>
<body>
<div id="root"></div>

<form name="frm">
<p>メッセージを入力してください</p>
<div id="li-count"></div>
<input type="text" id="message" name="message" size="40">
<input type="button" id="btn" value="OK" />
</form>

<ul id="ul_parents">
</ul>

<script>
window.onload = function(){

    document.getElementById('btn').onclick = function(){
      var mess01 = document.frm.message.value;
      console.log(mess01);
      const item2 = document.createElement('li');
      item2.classList.add ('li-btn');

      item2.innerHTML = mess01 + '<div id="delete-bt">delete</div>';
      const ul = document.querySelector('ul');
      ul.appendChild(item2);

      var elements = document.getElementsByClassName('li-btn');
      var elementsCt = elements.length;
      console.log("elementsCt",elementsCt);

      var counter = document.getElementById('li-count');
      counter.textContent = elementsCt;
      var ul_area = document.getElementById('ul_parents');
      var deleteBt01 = document.getElementById('delete-bt');
      deleteBt01.addEventListener('click',function(){

      //ここでクリックしたリストだけを消去したい、今は1個目のリストのdeleteを押すと、全部消えてしまう。
        ul_area.removeChild(elements[0]);
      });
    };
};
</script>
</body>
</html>

その後の修正

まだ直っていません。いくつかメッセージを入れて、
一番上をクリックすると全部消え、
一番下のものをクリックすると一つずつ消えます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>JS入力表示リスト</title>
</head>
<body>

<div id="root"></div>

<form name="frm">
<p>メッセージを入力してください</p>
<div id="li-count"></div>
<input type="text" id="message" name="message" size="40">
<input type="button" id="btn" value="OK" />
</form>

<ul id="ul_parents">
</ul>


<script>
window.onload = function(){
    document.getElementById('btn').onclick = function(){
      var mess01 = document.frm.message.value;
      console.log(mess01);
      const item2 = document.createElement('li');
      item2.classList.add ('li-btn');
      var elementsCt = 0;

      item2.innerHTML = mess01 + '<div class="delete-bt">delete</div>';
      const ul = document.querySelector('ul');
      ul.appendChild(item2);

      if(item2.classList.contains('li-btn')==true){
        console.log("何個?",item2.classList.contains('li-btn'));
        console.log("クラス何個?",item2.classList.length);
      }

      //li-btnという名前のクラスが 何個追加されたか表示 
      var elements = document.getElementsByClassName('li-btn');
      elementsCt = elements.length;

      //counter
      var counter = document.getElementById('li-count');
      counter.textContent = elementsCt;

      var ul_area = document.getElementById('ul_parents');
      var deleteBt01 = document.getElementsByClassName('delete-bt');
      for(let i=deleteBt01.length-1;i>=0;i--){
          deleteBt01[i].addEventListener('click',function(){
          deleteBt01[i].parentNode.remove();
        });
      }
    };
};
</script>
</body>
</html>

うまくいったもの

@aotoriii さんに教えていただき、うまく行ったものを下記に貼ります。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>JS入力表示リスト</title>
</head>
<body>
<div id="root"></div>
<form name="frm">
<p>メッセージを入力してください</p>
<div id="li-count"></div>
<input type="text" id="message" name="message" size="40">
<input type="button" id="btn" value="OK" />
</form>
<ul id="ul_parents">
</ul>
<script>
window.onload = function(){
    document.getElementById('btn').onclick = function(){
      var mess01 = document.frm.message.value;
      console.log(mess01);
      const item2 = document.createElement('li');
      item2.classList.add ('li-btn');
      var elementsCt = 0;
      item2.innerHTML = mess01 + '<div class="delete-bt">delete</div>';
      const ul = document.querySelector('ul');
      ul.appendChild(item2);
      if(item2.classList.contains('li-btn')==true){

      }
      //li-btnという名前のクラスが 何個追加されたか表示 
      var elements = document.getElementsByClassName('li-btn');
      elementsCt = elements.length;
      console.log("elementsCt",elementsCt);

      //counter
      var counter = document.getElementById('li-count');
      counter.textContent = elementsCt;
      var deleteBts = document.getElementsByClassName('delete-bt');
      const lastDeleteBt = deleteBts[deleteBts.length-1];

      lastDeleteBt.addEventListener('click',function(){
        if(lastDeleteBt.parentNode){
          lastDeleteBt.parentNode.remove();
        }
      });
    };
};
</script>
</body>
</html>

未だにわからないこと

  • click後だと何故clickしたものが一番最後にカウントされるのかがわからない
  • click前は、最後に追加したものが最後にカウントされる、またclick後でもdeleteBts[0].parentNode を見ると、順番に表示される。何故クリックしたものが最後にカウントされるんだろう?
      var deleteBts = document.getElementsByClassName('delete-bt');
      const lastDeleteBt = deleteBts[deleteBts.length-1];

      console.log("lastDeleteBt.parentNode--Click前",lastDeleteBt.parentNode);

      lastDeleteBt.addEventListener('click',function(){
        if(lastDeleteBt.parentNode){

          console.log("lastDeleteBt.parentNode--Click後",lastDeleteBt.parentNode);//click後だと何故クリックしたものが一番最後にカウントされるのかがわからない、deleteBts[0].parentNodeなどで見ると表示順に出てくるのに?

          console.log("1",deleteBts[0].parentNode);
          console.log("2",deleteBts[1].parentNode);
          console.log("3",deleteBts[2].parentNode);

          lastDeleteBt.parentNode.remove();
        }
      });
0

3Answer

このようなことをする時に、一般的にはどんな風にされているのか

js 内で html を直に書くのは(セキュリティなどの理由で)あまり推奨されないと思います。

item2.innerHTML = mess01 + '<div id="delete-bt">delete</div>'

ではなく

item2.appendChild(document.createTextNode(mess01));
const deleteBtn = document.createElement('div');
deleteBtn.classList.add('delete-bt');
deleteBtn.textContent = 'delete';
item2.appendChild(deleteBtn);

の方が良いです。

上記の書き方にはもう一つ利点があります。
下記のように deleteBtn を直に触りやすいです。

deleteBtn.addEventListener('click', function () {
  item2.remove();
});

何故クリックしたものが最後にカウントされるんだろう?

うまく行ったものの中で deleteBts が作られるのは
delete ボタンを押したときではなく OK ボタンが押されたときです。

OK ボタンを押すたびに新しい deleteBts が作られます。
OK ボタンを押すたびに新しい関数が作られて lastDeleteBt.addEventListener で登録されます。

delete ボタンを押したときにはカウントをしていないです。
OK ボタンを押したときにカウントされて
その結果をdelete ボタンを押したときにも使っています。


最後に質問内容とは関係ないのですが
せっかく form を使っているのにメッセージを入力後に
わざわざマウスで OK ボタンを押さないと投稿されません。

- <input type="button" id="btn" value="OK" />
+ <input type="submit" id="btn" value="OK" />
- document.getElementById('btn').onclick = function(){
+ document.querySelector('form[name="frm"]').addEventListener('submit', function (event) {
+   event.preventDefault(); // ブラウザ標準の機能で form が投稿されるのをキャンセルする

まとめ

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>JS入力表示リスト</title>
</head>
<body>
<div id="root"></div>

<form name="frm">
<p>メッセージを入力してください</p>
<div id="li-count"></div>
<input type="text" id="message" name="message" size="40">
<input type="submit" id="btn" value="OK" />
</form>

<ul id="ul_parents"></ul>

<script>
// すでに form タグがレンダリングされていることはわかっているので window.onload を省略しました
document.querySelector('form[name="frm"]').addEventListener('submit', function (event) {
  event.preventDefault(); // ブラウザ標準の機能で form が投稿されるのをキャンセルする

  const mess01 = document.frm.message.value;
  console.log(mess01);
  const item2 = document.createElement('li');
  item2.classList.add('li-btn');

  item2.appendChild(document.createTextNode(mess01));
  const deleteBtn = document.createElement('div');
  deleteBtn.classList.add('delete-bt');
  deleteBtn.textContent = 'delete';
  item2.appendChild(deleteBtn);

  const ul = document.querySelector('ul');
  ul.appendChild(item2);

  deleteBtn.addEventListener('click', function () {
    ul.removeChild(item2);
  });

  const elements = document.getElementsByClassName('li-btn');
  const elementsCt = elements.length;
  console.log("elementsCt", elementsCt);

  const counter = document.getElementById('li-count');
  counter.textContent = elementsCt;
});
</script>
</body>
</html>
2Like

Comments

  1. @okashi

    Questioner

    ありがとうございます!明日よく読んでみます。
    また、
    >js 内で html を直に書くのはあまり推奨されない
    >deleteBts が作られるのは
    delete ボタンを押したときではなく OK ボタンが押されたとき

    ありがとうございます。とても助かりました。
    明日もう一度よく読んでみようと思います。
  2. @okashi

    Questioner

    @rithmety さん
    今日再度コードを見て、理解することができました。
    document.create...というものにまだあまり慣れていないのですが、なるべく使うようにしようと思います。また、セキュリティについてもまだあまり考えたことがないため、色々勉強していこうと思います。
    ありがとうございました!

deleteボタンのidも連番にするおつもりでしょうか?
今のまま(id="delete-bt")ですと、
deleteボタンが全部同じidになってしまいます。

また、同じidのdeleteボタンをgetElementByIdで探すのがよろしくないかと思います。
メソッド名通りにgetElementgetElementsではなく)なので、
常に一つの要素しか取得できません。
そのため、追加ボタンが押された際に一番上のdeleteボタンにしかイベントを追加できません。
最初のdeleteボタン以外はクリックされても反応しないでしょう。

今はliを追加するたびに、
最初のdeleteボタンに対し、同じイベントを追加しています。
4件追加する = 「elements[0]の削除」を4回実行
になっているので、
一回のクリックで全部削除されているように感じます。

この点を証明するには、ログを追加してみるといいでしょう。

  deleteBt01.addEventListener('click',function(){
        //ここ追加
        console.log("elementsCt",elementsCt);
        //ここでクリックしたリストだけを消去したい、今は1個目のリストのdeleteを押すと、全部消えてしまう。
        ul_area.removeChild(elements[0]);
  });

イベントを追加する時のli-btnの数の出力を追加しましたが、
順次に1、2、3...と出力するはずです。

idの対策をしていただくのが一番ですが、
現在の状況で、「1個目のリストのdeleteを押すと1個目のリストだけ消去する」を実現させるには、こういう風に書けます。

  deleteBt01.addEventListener('click',function(){
    // 親要素が存在するなら
    if (deleteBt01.parentNode) {
        // 親要素を削除
        deleteBt01.parentNode.remove();
    }
  });

これなら何回呼ばれても、deleteボタンの親要素しか削除されません。

1Like

Comments

  1. @okashi

    Questioner

    ありがとうございます!1個目だけ付ける方法がわかったので、 次は連番をつけることができるようにしようと思います。
  2. @okashi

    Questioner

    @aotoriii さん、その後修正をして、上に、先ほど考えたものを追記しました。
    まだうまく動いていません。
    deleteボタンにクラスをつけ、クラスを回しているのですが、
    クリックが一番上だと全部消えてしまいます。

@okashi さん、コメントおよび修正後のコードを拝読しました。

deleteボタンにクラスをつけ、クラスを回しているのですが、
クリックが一番上だと全部消えてしまいます。

クラスを回していますが、実はループする必要がありません。
追加ボタンをクリックした際に、実現したいのは「最後に追加されたdeleteボタンに対しイベントを追加する」ことです。
全てのdeleteボタンに対して操作する必要がありません。

なのでまずは「最後のdeleteボタン」を取得するところからです。
例えばこのように、

        // クラス名で全てのdeleteボタンを取得
        const deleteBts = document.getElementsByClassName('delete-bt');
        // 最後のボタンだけ取得して宣言
        const lastDeleteBt = deleteBts[deleteBts.length-1];

取得できたら後は一緒です。

        // 最後のボタンに対してイベント追加
        lastDeleteBt.addEventListener('click',function(){
            if (lastDeleteBt.parentNode) {
                lastDeleteBt.parentNode.remove();
            }
        });

余談ですが、getElementsByClassNameで取得した全要素をループするには、
こういう風に書けます。

        // クラス名で全てのdeleteボタンを取得
        let deleteBts = document.getElementsByClassName('delete-bt');
        // 配列のインスタンスを作成
        deleteBts = Array.from(deleteBts);
        // 全要素をループ
        deleteBts.forEach(deleteBt => {
            deleteBt.addEventListener('click',function(){
                if (deleteBt.parentNode) {
                    deleteBt.parentNode.remove();
                }
            });
        });

参考:getElementsByClassNameの結果に対してforEachやmapを使う方法

この方法でも一行ずつの削除を実現できますが、
liを追加するたびに全てのdeleteボタンに対し操作しているので、
あまりおすすめできません。

1Like

Comments

  1. @okashi

    Questioner

    @aotoriii さん、大変ありがとうございます!なんとか動くようになり、記事にうまく動いたコードを追記ました。
    もう一つの方法もArray.fromを使ったことがなかったため、参考になりました。

    いまいちまだ理解できていないのが、// 最後のボタンだけ取得して宣言
    const lastDeleteBt = deleteBts[deleteBts.length-1];
    の部分について、これはクリックしたボタンを最後のボタンとするという意味でしょうか?

  2. お役に立ててよかったです。

    「最後のボタン」というのは'delete-bt'クラスの中で、一番下に表示されているdeleteボタンのことです。

    まず【deleteBts】にはdeleteボタンの集合(HTMLCollection)が入っています。

    【deleteBts.length - 1】は deleteBtsの要素数 - 1 で求めた最後の要素indexになります。
    【deleteBts[0]】だと最初のdeleteボタン(画面表示の一番上)が取得できるように、
    【deleteBts[deleteBts.length-1]】は最後のdeleteボタン(画面表示の一番下)が取得できます。

    もしliの挿入順が逆になった場合、この方法は使えません。
  3. @okashi

    Questioner

    @aotoriii さん、なんども丁寧にありがとうございます!
    「最後のボタン」については理解できたのですが、
    わからないのは、クリックした deleteボタンは、クリックした deleteボタンまでをgetElementsByClassNameで取得するということなのでしょうか?

    自分の認識では、deleteボタンをクリックすると、現在あるdeleteボタン全てを取得すると思っていたので、最後から1個目は、現在表示されているdeleteボタンの最後のものだと思っていました。
    それについて、また本文に追記で「未だにわからないこと」として書かせていただきました。
  4. 「最後のボタン」にカウントされるのはあくまでOKボタンがクリックされて、ボタンを追加した際の話です。
    その後deleteボタンがクリックされた時の動作と関係ありません。

    例えば3つ目のリストを作成する際はこうなります。
    ------------------------------
    OKボタン ←クリック

    ・リスト1
      + deleteボタン1

    ・リスト2
      + deleteボタン2

    ・リスト3      ←追加される
      + deleteボタン3
    ------------------------------

    この場合の動きをざっくり言うとこうです。
    1. OKボタンがクリックされる
    2. リスト3(deleteボタン3)を追加
    3. 'delete-bt'クラスの全要素を取得 → [deleteボタン1、deleteボタン2、deleteボタン3]
    4. 上記の最後の一つを取得 → deleteボタン3
    5. deleteボタン3に対して下記の内容のイベントを追加
     「deleteボタン3がクリックされたら、deleteボタン3(自分自身)の親(リスト3)を削除」

    以上でOKボタンがクリックされた際の処理が終わります。

    deleteボタン3がクリックされた際は、「自分自身の親(リスト)を削除する」ことのみ実行します。
    この時点は既に「最後のボタン」と関係ありません。

    また、@rithmetyさんがお書きになったようにメソッドで要素を追加した方が綺麗に動きます。
    追加してから全要素から探すではなく、
    最初から要素を保持していて、色々変えてから追加します。
    getElementsByClassNameからの取得ですと、予期せずに他のボタンを取得してしまう危険性がありますが、
    メソッドで追加する方法ですと他のボタンを触れてしまう恐れがございません。

Your answer might help someone💌