--I'm using Rails_6.0.0_. -Add a comment to the application that the product can be listed or purchased. --There is a place to comment on the detail page of each item. --Code Ruby required to save and send comments, and JavaScript to display saved comments immediately. -** The comment posting function has already been completed, and we will implement only the asynchronous function. ** **
--Implement using channel --Display comments asynchronously --The name (name), comment date (created_at), and comment content (text) are displayed in the comment.
A channel is a server-side mechanism that provides immediate update functionality. Set the data route and display the sent data on the client screen.
If you set the data route or write JavaScript to display the sent data, the sent data can be displayed asynchronously.
Execute the following command in the terminal
% rails g channel comment
(Enter the name of the file you create in comment
)
There are several files, but this time we will use the following two files.
app/channel/comment_channel.rb
It is a file for connecting the client and the server.
app/javascript/channels/comment_channel.js This file is used to display the data sent from the server on the client screen.
class MessageChannel < ApplicationCable::Channel
def subscribed
stream_from "comment_channel"
end
def unsubscribed
end
end
You can connect the server and client by writing stream_from" comment_channel "
.
A description of the controller. Since the comment implementation function that is not asynchronous has already been completed, explanations other than the description related to asynchronous are omitted.
The data I want to reflect in JS this time is as follows
--User nickname --Commented time --Comment text --Item id (necessary to determine which item to comment on)
It is necessary to pass these three pieces of information to JS by the controller.
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
@comment = Comment.new(comment_params)
@item = Item.find(params[:item_id])
@comments = @item.comments.includes(:user).order('created_at DESC')
if @comment.valid?
@comment.save
ActionCable.server.broadcast 'comment_channel', content: @comment, nickname: @comment.user.nickname, time: @comment.created_at.strftime("%Y/%m/%d %H:%M:%S"), id: @item.id
else
render "items/show"
end
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id, item_id: params[:item_id])
end
end
Only the following sentence was added in this implementation.
ActionCable.server.broadcast 'comment_channel', content: @comment, nickname: @comment.user.nickname, time: @comment.created_at.strftime("%Y/%m/%d %H:%M:%S"), id: @item.id
Since content
, ʻuser,
time and ʻid
are used in JS, I have defined them.
content
Defined by @comment: text, the text column of the Comment table, that is, the comment you entered.
user
I'm getting the nickname of the user associated with @comment. (We have an association with comment and user.)
time
The created_at column of the Comment table. Any date and time setting can be displayed by writing strftime ("% Y /% m /% d% H:% M:% S ")
. I referred to the following article.
I can't remember strftime (ruby)
item
JS needs to fire only on the page of the item you are currently viewing, so use it to determine that.
This time, I will describe it in the received ()
part of ʻapp / javascript / channels / comment_channel.js`.
app/javascript/channels/comment_channel.js
received(data) {
}
By writing data
in (), the value defined by the controller will be fetched. received
means to receive, so when you receive the data, execute the JS described in it! It means that.
Now, I will describe JS in this!
app/javascript/channels/comment_channel.js
//Get the URL of the currently open page
let url = window.location.href
//Slash(/)Extract elements for each
let param = url.split('/');
//In the case of this app, the item id comes at the very end of the URL and it is defined as a paramItem.
let paramItem = param[param.length-1]
//Parameter id(The id contained in the URL)Sent from the controller`data.id`Determine if
if (paramItem == data.id) {}
Conditional branching is done with the if statement. Next, I will write the contents of the process in the conditional branch. The content to be described is
--Create a div element --Display the generated element on the browser --Generate the text to be displayed
It is a flow like.
app/javascript/channels/comment_channel.js
//Get the ID of the div of the place to display
const comments = document.getElementById('comments');
//Create a div to be the same as an existing view file
const textElement = document.createElement('div');
textElement.setAttribute('class', "comment-display");
const topElement = document.createElement('div');
topElement.setAttribute('class', "comment-top");
const nameElement = document.createElement('div');
const timeElement = document.createElement('div');
const bottomElement = document.createElement('div');
bottomElement.setAttribute('class', "comment-bottom");
Use the createElement method to create a div element, and give each required class name with the setAttribute method.
By the way, the view to display comments is as follows
<div id='comments'>
</div>
<% @comments.each do |comment| %>
<div class='comment-display'>
<div class='comment-top'>
<div><%= comment.user.nickname %></div>
<div><%= l comment.created_at %></div>
</div>
<div class='comment-bottom'>
<p><%= comment.text %></p>
</div>
</div>
<% end %>
I have generated a div element, but it is not yet visible in the browser. Let's display it on the browser and create a parent-child relationship.
app/javascript/channels/comment_channel.js
//Display the generated HTML element in the browser
comments.insertBefore(textElement, comments.firstElementChild);
textElement.appendChild(topElement);
textElement.appendChild(bottomElement);
topElement.appendChild(nameElement);
topElement.appendChild(timeElement);
Use the insertBefire and appendChild methods. Parent element.insertBefore (element to add, where to add) Parent element .appendChild (element to add) So insertBefire can put an element anywhere, and appendChild can put an element at the end of the parent class.
If you want to see a little more detail, please see below [JavaScript] Difference between appendChild and insertBefore
After creating the div element, let's get the information to be displayed next.
app/javascript/channels/comment_channel.js
const name = `${data.nickname}`;
nameElement.innerHTML = name;
const time = `${data.time}`;
timeElement.innerHTML = time;
const text = `<p>${data.content.text}</p>`;
bottomElement.innerHTML = text;
The information to be displayed is put in each variable. data is the data for received (data) {}
. It is the value defined in the controller. You have defined content
, nickname
, and time
, respectively.
Overwrite existing elements with innerHTML.
The display is complete up to this point! However, there are two problems at this rate.
app/javascript/channels/comment_channel.js
const newComment = document.getElementById('comment_text');
newComment.value='';
It is a description that the ID of the comment input field is fetched and the value there is emptied.
app/javascript/channels/comment_channel.js
const inputElement = document.querySelector('input[name="commit"]');
inputElement.disabled = false;
You can click it many times by fetching the name attribute of the "Comment" button and setting it as disabled = false
.
app/javascript/channels/comment_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("CommentChannel", {
connected() {
},
disconnected() {
},
//↓ When you receive the data, execute it.
received(data) {
let url = window.location.href
let param = url.split('/');
let paramItem = param[param.length-1]
if (paramItem == data.id) {
const comments = document.getElementById('comments');
const comment = document.getElementsByClassName('comment-display');
//Creating elements to use
const textElement = document.createElement('div');
textElement.setAttribute('class', "comment-display");
const topElement = document.createElement('div');
topElement.setAttribute('class', "comment-top");
const nameElement = document.createElement('div');
const timeElement = document.createElement('div');
const bottomElement = document.createElement('div');
bottomElement.setAttribute('class', "comment-bottom");
//Display the generated HTML element in the browser
comments.insertBefore(textElement, comments.firstElementChild);
textElement.appendChild(topElement);
textElement.appendChild(bottomElement);
topElement.appendChild(nameElement);
topElement.appendChild(timeElement);
//Generate text to display
const name = `${data.nickname}`;
nameElement.innerHTML = name;
const time = `${data.time}`;
timeElement.innerHTML = time;
const text = `<p>${data.content.text}</p>`;
bottomElement.innerHTML = text;
//After sending a comment, leave the comment field empty
const newComment = document.getElementById('comment_text');
newComment.value='';
//Allows you to press the button many times
const inputElement = document.querySelector('input[name="commit"]');
inputElement.disabled = false;
}
}
});
I thought it was completed, but at the time of writing, I found a number of mistakes and some unclear descriptions. I understand how important refactoring is. There may be incorrect statements, but I hope it helps someone.
Recommended Posts