Skip to content

Instantly share code, notes, and snippets.

@stephlaf
Last active April 15, 2023 18:38
Show Gist options
  • Save stephlaf/3c3902773bfbdf3e46a552c8f14724a6 to your computer and use it in GitHub Desktop.
Save stephlaf/3c3902773bfbdf3e46a552c8f14724a6 to your computer and use it in GitHub Desktop.

Styling your chat

So that the messages appear on the right if you are the sender, and on the left if you are the receiver, both with their own background-color.

Screen Shot

For that, we will need to add a few things in a number of files, namely to pass infos to our Stimulus chatroom_subscription_controller

Let's start by passing the current_user id as a value to Stimulus from our show page.

In app/views/chatrooms/show.html.erb

  <div class="container chatroom"
    data-controller="chatroom-subscription"
    data-chatroom-subscription-chatroom-id-value="<%= @chatroom.id %>"
    data-chatroom-subscription-current-user-id-value="<%= current_user.id %>">

Next, we'll wrap each message inside two div that will serve to position the message left or right, using Bootstrap classes, and to style it differently depending on the current_user being the message's sender or receiver.

  <% @chatroom.messages.each do |message| %>
    <div class="message-row d-flex <%= message.sender?(current_user) ? 'justify-content-end' : 'justify-content-start' %>">
      <div class="<%= message.sender?(current_user) ? 'sender-style' : 'receiver-style' %>">
        <%= render "messages/message", message: message %>
      </div>
    </div>
  <% end %>

Notice we use a message#sender? instance method in the display logic, to determine if the current_user is the message's sender. We need to define this method in app/models/message.rb

  def sender?(a_user)
    user.id == a_user.id
  end

Notice also that we are using three classes: .message-row, .sender-style and .receiver-style, which have not yet been defined.
Let's create a new file and define those classes.

touch app/assets/stylesheets/components/_message.scss

In the newly created _message.scss

  // To style the wrapping div
  .message-row {
    margin-bottom: 10px;
    padding: 8px;
  }

  // To style the message itself
  .sender-style, .receiver-style {
    width: 70%;
    max-width: 400px;
    border-radius: 4px;
    padding: 8px;
    min-height: 80px;
  }

  .sender-style {
    background-color: lightsalmon;
  }

  .receiver-style {
    background-color: lightcoral;
  }

⚠️ Don't forget to import this new component in _index.scss

⚠️ Note that the last two steps will style the rendered messages only when the page is refreshed.

Which means we'll need to tackle the same logic in our Stimulus chatroom_subscription_controller. In order to do so, we will need access to the message's user_id (aka: sender_id). Let's pass it through the cable as part of the broadcasted data

In app/controllers/messages_controller.rb

  # [...]
  if @message.save
    ChatroomChannel.broadcast_to(
      @chatroom,
      message: render_to_string(partial: "message", locals: { message: @message }),
      sender_id: @message.user.id
    )
  # [...]

And now, the Stimulus part 😃

From the chatrooms/show.html.erb we are now passing the current_user.id as a value to our controller, so let's define it.

In app/javascript/controllers/chatroom_subscription_controller.js

export default class extends Controller {
  static values = { chatroomId: Number, currentUserId: Number }
  // [...]

For the next part, we'll be working in the#insertMessageAndScrollDown(data) function

First, let's build the logic to know if the message was sent by the current_user, using the sender_id from the data, and the currentUserIdValue

#insertMessageAndScrollDown(data) {
  // Logic to know if the sender is the current_user
  const currentUserIsSender = this.currentUserIdValue === data.sender_id

  // [...]
}

We will now able to figure out what style Class the message should have, and where its wrapping div should be positioning it on the page.

Let's create a new #buildMessageElement() function to build our complete message with its two wrapping div, passing it the currentUserIsSender Boolean, and the message String coming from the data

#buildMessageElement(currentUserIsSender, message) {
  return `
    <div class="message-row d-flex ${this.#justifyClass(currentUserIsSender)}">
      <div class="${this.#userStyleClass(currentUserIsSender)}">
        ${message}
      </div>
    </div>
  `
}

From this function, we are calling two other functions which will also use the currentUserIsSender Boolean to return us the relevant classes to position and style our message. Let's define those.

#justifyClass(currentUserIsSender) {
  return currentUserIsSender ? "justify-content-end" : "justify-content-start"
}

#userStyleClass(currentUserIsSender) {
  return currentUserIsSender ? "sender-style" : "receiver-style"
}

And finally, let's call our #buildMessageElement(currentUserIsSender, message) function, and see the magic happen ✨

#insertMessageAndScrollDown(data) {
  // Logic to know if the sender is the current_user
  const currentUserIsSender = this.currentUserIdValue === data.sender_id

  // Creating the whole message from the `data.message` String
  const messageElement = this.#buildMessageElement(currentUserIsSender, data.message)

  // Inserting the `message` in the DOM
  this.messagesTarget.insertAdjacentHTML("beforeend", messageElement)
  this.messagesTarget.scrollTo(0, this.messagesTarget.scrollHeight)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment