Search code examples
javascriptruby-on-railscoffeescriptactioncable

rails remove element in real time


Got a link that removes a message from the current_users screen:

= link_to '[x]', msg, method: :delete, remote: true, class: "del-link"

It triggers this coffeescript-funktion:

delete_message = () ->
$('.del-link').on 'ajax:success', (event) ->
  event.preventDefault()
  $(this).closest('.message').remove()
  return

and this rails-method:

def destroy
  @msg = Msg.find(params[:id])
  @msg.destroy

  respond_to do |format|
    format.html { redirect_to chat_path }
    format.json { head :no_content }
    format.js   { render :layout => false }
  end
end

But how would it be done, that the message is removed on every users screen, e.g. using ActionCable?

The coffeescript for the chat:

App.proom = App.cable.subscriptions.create "ProomChannel",
  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) ->
    unless data.msg.blank?
        $('#messages-table').append data.msg
        scroll_bottom()


 $(document).on "turbolinks:load", ->
   submit_msg()
   scroll_bottom()
   delete_message()

the channel.rb

class ProomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "proom_channel"
  end

  def unsubscribed
  end
end

Approach

So I added this to the destroy-action of the controller

if msg.destroy
  ActionCable.server.broadcast 'proom_channel', msg: @msg
end

Also I added an ID to every message-div-element containing the id of the message-record to find and remove it from the DOM with the following line

$("msg_#{msg.id}").remove()

But now I don't know where to put it.


Solution

  • I am not able to give you right now the complete answer, but I will update my answer later if you do not find a solution:

    Like when you create a message with action cable, after saving the message in the DB, you trigger an ActionCable.server.broadcast 'messages' that will execute some JS included for example in the User Browser asset pipeline file app/assets/javascripts/channels/messages.js

    This is my message controller, once I save the message, I start the Server.broadcast process. It will trigger the process in my messages_channel.rb that will update all clients that are subscribed to that channel.

      def create
        message = Message.new(message_params)
        message.user = current_user 
        chatroom = message.chatroom
        if message.save
            ActionCable.server.broadcast 'messages',
            message: message.content,
            user: message.user.name,
            chatroom_id: message.chatroom_id,
            lastuser: chatroom.messages.last(2)[0].user.name
          head :ok
        end
      end
    

    Image and text is from the following article of Sophie DeBenedetto

    We add our new subscription to our consumer with App.cable.subscriptions.create. We give this function an argument of the name of the channel to which we want to subscribe, MessagesChannel.

    When this subscriptions.create function is invoked, it will invoke the MessagesChannel#subscribed method, which is in fact a callback method.

    MessagesChannel#subscribed streams from our messages broadcast, sending along any new messages as JSON to the client-side subscription function.

    Then, the received function is invoked, with an argument of this new message JSON. The received function in turn calls a helper function that we have defined, renderMessage, which simply appends new messages to the DOM, using the $("#messages") jQuery selector, which can be found on the chatroom show page.

    The Message Channel

    The channel will call the messages.js function received, that will append the div to the DOM, you will need to call an alternative action in messages.js.

    App.messages = App.cable.subscriptions.create('MessagesChannel', {      
      received: function(data) {
          // code executed - the parameters will be in data.chatroom_id and 
          // data.user, data.message etc...
      }
    });
    

    So you will need to call the channel from your action messages#destroy, have in messages.js a specific function to remove the div from the DOM. I don't know if you will need to create a specific server side channel or you can just edit your present messages_channel.rb to include a specific function.. You need to read the guides and figure this out.. I may try this in the future, but right now I can't

    Or an easy alternative, just write some js in messages.js to solve this and have the div removed when required.. for example the messages#destroy action can pass a parameter and if that parameters is present, you will remove the message, instead of adding it

    https://github.com/rails/rails/tree/master/actioncable#channel-example-1-user-appearances