Skip to content
This repository has been archived by the owner on Sep 19, 2020. It is now read-only.

Commit

Permalink
feat: finish out blog post
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewmcodes committed Sep 19, 2020
1 parent b9049f6 commit 95a1e08
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ gem "jbuilder", "~> 2.7"
gem "pg", ">= 0.18", "< 2.0"
gem "puma", "~> 4.1"
gem "rails", "~> 6.0.3", ">= 6.0.3.3"
# gem 'redis', '~> 4.0'
# gem "redis", "~> 4.0"
gem "turbolinks", "~> 5"
gem "webpacker", "~> 5.2"

Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ GEM
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.2.2)
regexp_parser (1.7.1)
rexml (3.2.4)
rubocop (0.91.0)
Expand Down Expand Up @@ -195,6 +196,7 @@ DEPENDENCIES
pg (>= 0.18, < 2.0)
puma (~> 4.1)
rails (~> 6.0.3, >= 6.0.3.3)
redis (~> 4.0)
standard (~> 0.6)
turbolinks (~> 5)
tzinfo-data
Expand Down
28 changes: 9 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
# README

This README would normally document whatever steps are necessary to get the
application up and running.
Follow Evil Front Parts 1-3 tutorial

Things you may want to cover:
## Run

* Ruby version
```sh
overmind start -f Procfile
```

* System dependencies

* Configuration

* Database creation

* Database initialization

* How to run the test suite

* Services (job queues, cache servers, search engines, etc.)

* Deployment instructions

* ...
- [Blog Post - Part 1](https://evilmartians.com/chronicles/evil-front-part-1)
- [Blog Post - Part 2](https://evilmartians.com/chronicles/evil-front-part-2)
- [Blog Post - Part 3](https://evilmartians.com/chronicles/evil-front-part-3)
- [Example Repo](https://github.com/evilmartians/evil_chat)
6 changes: 6 additions & 0 deletions app/channels/application_cable/connection.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user

def connect
self.current_user = request.session.fetch("username", nil)
reject_unauthorized_connection unless current_user
end
end
end
18 changes: 18 additions & 0 deletions app/channels/chat_channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat"
end

# Called when message-form contents are received by the server
def send_message(payload)
message = Message.new(author: current_user, text: payload["message"])

ActionCable.server.broadcast "chat", message: render(message) if message.save
end

private

def render(message)
ApplicationController.new.helpers.c("message", message: message)
end
end
13 changes: 13 additions & 0 deletions app/javascript/client/cable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createConsumer } from "@rails/actioncable";

let consumer;

const createChannel = (...args) => {
if (!consumer) {
consumer = createConsumer();
}

return consumer.subscriptions.create(...args);
};

export default createChannel;
21 changes: 21 additions & 0 deletions app/javascript/client/chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import createChannel from "./cable";

let callback; // declaring a variable that will hold a function later

const chat = createChannel("ChatChannel", {
received({ message }) {
if (callback) callback.call(null, message);
},
});

// Sending a message: "perform" method calls a respective Ruby method
// defined in chat_channel.rb. That's your bridge between JS and Ruby!
const sendMessage = (message) => chat.perform("send_message", { message });

// Getting a message: this callback will be invoked once we receive
// something over ChatChannel
const setCallback = (fn) => {
callback = fn;
};

export { sendMessage, setCallback };
4 changes: 2 additions & 2 deletions app/javascript/components/chat/chat.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import "components/messages/messages";
import "components/message-form/message-form";
import "../messages/messages";
import "../message-form/message-form";
import "./chat.pcss";
56 changes: 56 additions & 0 deletions app/javascript/components/message-form/message-form.js
Original file line number Diff line number Diff line change
@@ -1 +1,57 @@
/* eslint no-param-reassign: off */

// we need to import sendMessage from our client/chat.js
import { sendMessage } from "../../client/chat";
import "./message-form.pcss";

const isMac = navigator.platform.match(/Mac/) != null;

const handleLineBreak = (input) => {
input.value = `${input.value}\n`;
};

const handleSubmit = (input) => {
const { value } = input;

if (value.length === 0) {
return;
}

// Invokes sendMessage, that, in turn, invokes Ruby send_message method
// that will create a Message instance with ActiveRecord
sendMessage(input.value);

input.value = "";
input.focus();
};

const form = document.querySelector(".js-message-form");

if (form) {
const input = form.querySelector(".js-message-form--input");
const submit = form.querySelector(".js-message-form--submit");

// You can send a message with [Enter] or [Cmd/Ctrl + Enter]
input.addEventListener("keydown", (event) => {
if (event.code !== "Enter") {
return;
}

event.preventDefault();

const { altKey, ctrlKey, metaKey, shiftKey } = event;
const withModifier = altKey || shiftKey || (isMac ? ctrlKey : metaKey);

if (withModifier) {
handleLineBreak(input);
} else {
handleSubmit(input);
}
});

// Or by cicking a button
submit.addEventListener("click", (event) => {
event.preventDefault();
handleSubmit(input);
});
}
24 changes: 23 additions & 1 deletion app/javascript/components/messages/messages.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
import "components/message/message"; // message is nested, so we import it here
import { setCallback } from "../../client/chat";
import "../message/message"; // message is nested, so we import it here
import "./messages.pcss";

const scrollToBottom = (element) => {
// eslint-disable-next-line
element.scrollTop = element.scrollHeight;
};

const messages = document.querySelector(".js-messages");

if (messages) {
const content = messages.querySelector(".js-messages--content");

scrollToBottom(content);

// Telling `chat.js` to call this piece of code whenever a new message is received
// over ActionCable
setCallback((message) => {
content.insertAdjacentHTML("beforeend", message);

scrollToBottom(content);
});
}
4 changes: 2 additions & 2 deletions app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@

import "../init";
import "../components/page/page";
import "components/auth-form/auth-form";
import "components/chat/chat";
import "../components/auth-form/auth-form";
import "../components/chat/chat";

0 comments on commit 95a1e08

Please sign in to comment.