# Terminal
rails g channel list
# layouts/application.html.erb
<%= action_cable_meta_tag %>
# channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.id
end
private
def find_verified_user
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
end
end
# channels/list_channel.rb
class ListChannel < ApplicationCable::Channel
def subscribed
list = current_user.lists.find_by(params[:list_id])
stream_for list
end
def unsubscribed
Rails.logger.info "Disconnected from server"
end
end
# javascript/controllers/list_channel_controller.js
import { Controller} from "stimulus"
import consumer from '../channels/consumer'
export default class extends Controller {
connect() {}
initialize() {
this.subscription()
}
disconnect() {
this.subscription().unsubscribe()
this.subscription().disconnected()
}
subscription() {
if (this._subscription == undefined) {
let _this = this
this._subscription = consumer.subscriptions.create({ channel: "ListChannel", list_id: this.data.get("id") }, {
connected() {
// Called when the subscription is ready for use on the server
console.log(`connected to ${_this.data.get("id")}`)
},
disconnected() {
// Called when the subscription has been terminated by the server
console.log(`disconnected from ${_this.data.get("id")}`)
},
received(data) {
// Called when there's incoming data on the websocket for this channel
console.log(`received from ${_this.data.get("id")}`)
let type = data.type
if (type == "create") {
_this.createItem(data.column_id, data.item)
} else if (type == "update") {
_this.updateItem(data.column_id, data.column)
} else if (type == "destroy") {
_this.destroyItem(data.item_id)
}
}
});
}
return this._subscription
}
createItem(column_id, item) {
let column = document.getElementById(`column_${column_id}`)
column.insertAdjacentHTML("afterBegin", item)
}
updateItem(column_id, items) {
let column = document.getElementById(`column_${column_id}`)
column.innerHTML = items
}
destroyItem(item_id) {
let item = document.getElementById(`item_${item_id}`)
item.remove()
}
}
# items_controller.rb
def create
@item = @column.items.new(item_params)
if @item.save
ListChannel.broadcast_to(@list, type: 'create',
item: ItemsController.render(@item, locals: { list: @list, column: @column }),
column_id: @column.id)
redirect_to @list, notice: 'Item was successfully created.'
else
render :new
end
end
def update
params[:positions].uniq.each_with_index do |id, index|
@list.items.find(id).update(position: index + 1)
end
if @item.update(item_params)
ListChannel.broadcast_to(@list, type: "update",
column_id: @column.id,
column: ItemsController.render(@column.items.order(position: :asc),
locals: { list: @list, column: @column }))
new_column_items = @list.columns.find(params[:item][:column_id]).items.order(position: :asc)
ListChannel.broadcast_to(@list, type: "update",
column_id: params[:item][:column_id],
column: ItemsController.render(new_column_items,
locals: { list: @list, column: @column }))
respond_to do |format|
format.html { redirect_to @list, notice: 'Item was successfully updated.' }
format.json {}
end
else
render :edit
end
end
def destroy
if @item.destroy
ListChannel.broadcast_to(@list, type: "destroy", item_id: @item.id)
end
redirect_to @list, notice: 'Item was successfully destroyed.'
end
# views/lists/show.html.erb
<%= content_tag :div, class: :row, data: { "list-channel-id": @list.id, controller: 'list-channel draggable' } do %>
<% @list.columns.each do |column| %>
<div class='col'>
<div class='card'>
<h5 class='card-title'><%= column.name %></h5>
<div class='card-body'>
<%= content_tag :ul, class: 'card-text dropzone', id: dom_id(column), data: { id: column.id, target: 'draggable.column' } do %>
<%= render column.items, list: @list, column: column %>
<% end %>
</div>
<%= link_to "New Item", [:new, @list, column, :item] %>
</div>
</div>
<% end %>
<% end %>
# views/items/_item.html.erb
<%= content_tag :li, id: dom_id(item), data: { target: 'draggable.item',
id: item.id,
url: list_column_item_path(list, column, item) } do %>
<div class="card">
<div class="card-body">
<p class="card-text">
<%= item.content %>
</p>
<%= link_to 'delete', [list, column, item], method: :delete %>
</div>
</div>
<% end %>
# javascript/channels/list_channel.js
// import consumer from "./consumer"
// document.addEventListener("turbolinks:load", function () {
// let list_room = document.getElementById('list_room')
// if (list_room) {
// consumer.subscriptions.create( { channel: "ListChannel", list_id: list_room.dataset.id }, {
// connected() {
// // Called when the subscription is ready for use on the server
// console.log(`connected to ${list_room.dataset.id}`)
// },
// disconnected() {
// // Called when the subscription has been terminated by the server
// console.log(`disconnected from ${list_room.dataset.id}`)
// },
// received(data) {
// // Called when there's incoming data on the websocket for this channel
// console.log(`received from ${list_room.dataset.id}`)
// }
// });
// }
// })
// document.addEventListener("turbolinks:before-visit", function () {
// let list_room = document.getElementById('list_room')
// if (list_room) {
// consumer.list_channel.unsubscribe()
// consumer.list_channel.disconnected()
// }
// })