mako1111
@mako1111 (mako chan)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

ActionCableでコメントを削除する

解決したいこと

現在ActionCableコメント投稿機能を実装中。
投稿したコメントには削除ボタンが実装されており、これを削除する

現状の実装

❶コメント投稿時に用意したmessage_channel.rbと同様に
delete_channel.rbを用意しサーバーと繋げてあげる記述を行う
❷コントローラーではコメント削除後にdelete_channelへ@commentのデータを渡してあげる
❸delete_channnel.jsにてコメント削除の記述

該当するソースコード

#message_channnel.js
import consumer from "./consumer"

consumer.subscriptions.create("MessageChannel", {
  connected() {
    // Called when the subscription is ready for use on the server
  },

  disconnected() {
    // Called when the subscription has been terminated by the server
  },

  received(data) {
    const html = `<p><a href="/#/">@${data.user.nickname}</a></p>
                  <span style="font-weight:bold;">${data.content.content}</span>
                  &nbsp;${data.date}
                  &nbsp;<button id="<%=comment.post.id%>" class="delete-btn">削除</button>`;

    const messages = document.getElementById('comment-box');
    const newMessage = document.getElementById('comment_content');
    messages.insertAdjacentHTML('beforebegin', html);
    newMessage.value='';
  }
});
#delete_channnel.js

import consumer from "./consumer"

consumer.subscriptions.create("DeleteChannel", {
  connected() {
    // Called when the subscription is ready for use on the server
  },

  disconnected() {
    // Called when the subscription has been terminated by the server
  },

  received: function(data) {

  },


  window.addEventListener('load', function(){
    const deleteBtn = document.getElementById("2")

    deleteBtn.addEventListener('click', function() {
      console.log("click OK")
    })
  })

});
#delete_channnel.rb

class DeleteChannel < ApplicationCable::Channel
  def subscribed
    stream_from "delete_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

end
#comments_controller.rb

  def destroy
    @post = Post.find(params[:post_id])
    @comment = Comment.find(params[:id])
    if @comment.destroy
      ActionCable.server.broadcast 'delete_channel', id: @comment
    end
  end
#_index.html.erb
    <div class="collapse" id="collapseExample">
            <% @comments.offset(2).each do |comment| %>
              <% unless comment.id.nil? %>
                <li class="comment-container">
                    <div class="comment-box">
                        <div class="comment-text">
                            <p><%= link_to "@#{comment.user.nickname}", user_path(comment.user.id) %></p>
                            <div class="comment-entry">
                                <span style="font-weight:bold;"><%= comment.content %></span>
                                &nbsp;<%= comment.created_at.to_s(:datetime_jp) %>
                                <% if comment.user == current_user %>
                                  <%= link_to post_comment_path(comment.post_id, comment.id), method: :delete, remote: true  do %>
                                  &nbsp;<button id="<%=comment.post.id%>" class="delete-btn">削除</button>
                                <% end %>
                            <% end %>
                        </div>
                    </div>
                </div>
            </li>
          <% end %>
        <% end %>
    </div>

自分で試したこと

ActionCableを用いて、チャンネル内の記述を行い、実装済みのコメント削除ボタンをクリック後に動作が行われる結果を得たいと思い、アクションプランを立てています。
どういった解決策がありますでしょうか?
また、方向性はあっていますでしょうか?
お答えいただけましたら幸いです。。

0

3Answer

まずエラーメッセージをよく読んでみることです。 SyntaxError (構文エラー)なら原因は必ずソースコードの中にあります。

SyntaxError: /Users/max/suketto/app/javascript/channels/delete_channel.js: Unexpected token, expected "," (17:8)
delete_channel.js
  window.addEventListener('load', function(){
     // ^ ここがカンマでなければならない(?)というエラー

delete_channel.js の一見正しそうな箇所でエラーが出ていますが、構文エラーは本当の原因より後の行で問題が現れることがよくあります。

エラーより前のコードをよく読んでみると閉じカッコを忘れている部分が見つかります。

delete_channel.js
consumer.subscriptions.create("DeleteChannel", { // この開きカッコを……
  // (略)

  received: function(data) {

  },
  // ここで `});` と閉じるべきだが、間違ってもっと後ろの行に書いてある

  // まだ開きカッコの中なのでこうは書けない
  // `window,` なら書けるので、カンマでなければならないというエラーになる
  window.addEventListener('load', function(){

方向性ですが、チャットのようにコメント削除が他の閲覧者にもリアルタイムで反映されてほしいならこれでいいと思います。リアルタイム性が重要でない掲示板のようなアプリケーションなら ActionCable を使っても複雑になるだけです。

0Like
import consumer from "./consumer"

consumer.subscriptions.create("DeleteChannel", {
  connected() {
    // Called when the subscription is ready for use on the server
  },

  disconnected() {
    // Called when the subscription has been terminated by the server
  },

  received: function(data) {

  },
});

  window,addEventListener('load', function(){
    const deleteBtn = document.getElementById("2")
    deleteBtn.addEventListener('click', function() {
      console.log("click OK")
    })
  })

師匠いつもありがとうございます!
ご指摘の通り記述し問題なく解決し、クリック時に削除処理を行いリアルタイムで反映させるように書いていこうとしたところ、これでは反映されず色々変更を加えていました。
この方法ではなくreceivedで受け取ったのち、削除機能を実装する方法を取るべきでしょうか。。なかなか理解できずにいます。

0Like

window,addEventListener カンマに変えるのではなくピリオドのままにしてください。カンマに変えろというエラーメッセージは他の場所のコードが壊れていた影響によるコンパイラの間違いです。ここをブラウザの開発者ツールで見るとエラーが出ているはずです。


received で受け取るのは誰かが削除を実行したというイベントです。誰かが削除したら自分も削除できるようになるというのは筋が通りません(そもそも初期状態では誰も削除できないのでイベントも来ません)。

received を受け取ったときは、誰かが削除したメッセージがまだ自分の画面に残っていれば非表示にする処理をしましょう。

0Like

Comments

  1. @mako1111

    Questioner

    コンパイラの部分修正しました。ありがとうございます!

    アドバイス頂けた部分でもし師匠でしたらどうコードを書きますか?
    お時間あれば一度教えて頂けると幸いです。
    idを受け取ったのち、条件分岐を使い記述したのですがどうやら違うようでして
  2. コメントのコンテナ要素にあらかじめコメント ID を入れておいて、

    <li class="comment-container" id="comment-#{comment.id}">

    それを見つけて削除します。

    received(data) {
    const container = document.getElementById(`comment-${data.commentId}`);
    if (container) {
    container.parentNode.removeChild(container);
    }
    },
  3. @mako1111

    Questioner

    勉強になります。ありがとうございます!

    container = コメント投稿の要素
    これをremoveChildで消すということですね

    早速参考にやってみたのですが、反映はされずコントローラーで情報を送信する際に違いがあったのかなと仮説を立て記述を変更してみたのですが、変わりはなくconsole上にもエラーは出ていませんでした。
  4. デバッグするときは仮説を立てる前に今のコードがどう動いているかみっちり確認してください。

    console.log(data.commentId);
    console.log(container);

    などと書いてみて、コメント ID を正しく受け取れているか、削除したい要素をきちんと取得できたか見てください。
  5. @mako1111

    Questioner

    def destroy
    @post = Post.find(params[:post_id])
    @comment = Comment.find(params[:id])
    if @comment.destroy
    ActionCable.server.broadcast 'delete_channel', id: @comment
    end
    end

    ---

    import consumer from "./consumer"
    consumer.subscriptions.create("DeleteChannel", {
    connected() {
    // Called when the subscription is ready for use on the server
    },
    disconnected() {
    // Called when the subscription has been terminated by the server
    },
    received(data) {
    const container = document.getElementById(`comment-${data.id}`);
    if (container) {
    container.parentNode.removeChild(container);
    }
    },
    });

    ---

    <li class="comment-container" id="comment-${data.id}">

    ---

    ご返信ありがとうございます。
    debuggerではdataとして@commentの情報は入手できており、その後の(`comment-${data.id}`)部分で期待する結果が得られず、nullとなっていますね。
    HTML内の#を$に変えましたが変わりはなかったです。
    コメントのIDに絞った取得方法もやってみましたが変わりなしでした、、
  6. リストのほうは ERB 記法で書かないとだめでした。失礼しました🙇‍♂️

    <li class="comment-container" id="comment-<%= comment.id %>">
  7. 文字列の中にコードを埋め込む記法について整理すると、

    JavaScript:
    `abc ${JS のコード} def`

    Ruby:
    "abc #{Ruby のコード} def"

    ERB:
    abc <%= Ruby のコード %> def

    です。(文字列のクオートが JavaScript では
    「`」 で Ruby は 「"」 になっていますが、この通りでないと動きません)

    _index.html.erb の中では ERB 記法で書かなければいけないということでした。
  8. @mako1111

    Questioner

    ありがとうございます。
    こちらの方でも確認して文字列として認識されていたため原因がわかってよかったです。

    <li class="comment-container" id="comment-<%=comment.id%>">


    ---


    import consumer from "./consumer"
    window.addEventListener('load', function(){
    consumer.subscriptions.create("DeleteChannel", {
    connected() {
    // Called when the subscription is ready for use on the server
    },

    disconnected() {
    // Called when the subscription has been terminated by the server
    },


    received(data) {
    const container = document.getElementById(`comment-${data.id}`);
    if (container) {
    container.parentNode.removeChild(container);
    }
    },
    });
    })


    ---


    値は問題なく取得でき、削除後の変数containerも空であることが確認できました。
    まだ反映がされていなかったので、ページが読み込まれる前に実行されている可能性を考えwindow.addEventListenerで処理をしました。
    教えて頂いた内容を反映し、全体の処理はこれで良いかと思ったのですが、まだ問題がありそうです^^;
  9. 変数 container が空になることはありません!

    received(data) {
    const container = document.getElementById(`comment-${data.id}`);
    console.log(container); // ここで変数 container は空ではないはず
    if (container) {
    container.parentNode.removeChild(container);
    console.log(container); // 画面から削除した後も要素は変数 container に残るので空ではない
    }
    },

    空になるなら document.getElementById(`comment-${data.id}`); が要素を見つけられなかったということです。

    コメント ID が食い違っている可能性がありますが、それ以外の記述のミスのせいかもしれません。
  10. @mako1111

    Questioner

    師匠、解決いたしました!

    コントローラー→JS
    送信data:@comment.id→受信(`comment-${data}`)
    この場合、id情報だけではなく、id:137のように余分なデータも送られていた

    (`comment-${data}`)を(`comment-${data.id}`)とすることで
    コメントのIDの137のみ取得する

    よって解決しました!
    本当に勉強になりました。。ありがとうございました。。
  11. おめでとうございます! お疲れ様でした。
  12. @mako1111

    Questioner

    この質問も最後にしたかったのですが、あと一点課題がありまして、、
    リアルタイムでコメント投稿をする際、削除ボタンも一緒に送信しています。
    その削除ボタンに機能を持たせることは可能でしょうか?
    現状は削除ボタンのIDにコメントIDを持たせるように記述はしてあり、HTMLならlink_toで機能を持たせる流れになっています。。 ^^;

    received(data) {
    const html = `<p><a href="/#/">@${data.user.nickname}</a></p>
    <span style="font-weight:bold;">${data.content.content}</span>
    &nbsp;${data.date}
    &nbsp;<button id="${data.id}" id="delete-btn">削除</button>`;

    const messages = document.getElementById('collapseExample');
    const newMessage = document.getElementById('comment_content');
    messages.insertAdjacentHTML('beforeend', html);
    newMessage.value='';
    }
  13. insertAdjacentHTML で要素を挿入した後、その要素に onclick イベントハンドラを追加してハンドラ内で削除のリクエストを送るか、
    あるいは Rails 側で link_to を使ったボタン部分の HTML 文字列を生成しておいて insertAdjacentHTML するか、ですね。

    詳細はコメント欄では手狭なので新しく質問を投稿してください。
  14. @mako1111

    Questioner

    承知しました。

Your answer might help someone💌