Hey! Im working on this problem where I have to create a function in Phoenix where I run it in the IEX with a string, itll show that string on multiple browser windows (which are opened to that port that the server is running on). I have received hints that I might have to use LivewView or PubSub. I am just wondering if you guys have any ideas on how to implement this!
Seems like a very specific problem statement!
I think you’d be well served using Phoenix Channels along with your Endpoint’s broadcast/3 function.
Hi!
I was wondering if you maybe give me some more steps to follow? I am pretty new to this and would like to understand on how I would get this rolling!
Channels seems a good solution for your requirements, I’d suggest you to make a fresh new project in order to learn about them, in isolation. (you dont need a db / ecto for that.)
@axelson linked the guide, which is the must-read starting point and contains a very clear tutorial. With it I’d say you’re half way through Enjoy the challenge!
I ended up setting up a simple channel chat room following the guide but I am stuck on if I create a broadcast (message) function in my test.ex, how would i connect this function to my channel to display message on my browser window?
Does this help https://www.youtube.com/watch?v=KAqUy3nsXfg?
This video seems to be going over the working chat I have already created. So how would I send a message from my server and make it display on my browser window?
Usually You would use the endpoint to broadcast anywhere from Phoenix…
https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#c:broadcast/3
But in your case, You might as well create a channel, and use Phoenix pubsub directly.
https://hexdocs.pm/phoenix/1.1.3/Phoenix.PubSub.html
What I usually do is create a notifier module, like this…
defmodule KokoWeb.Notifier do
require Logger
alias KokoWeb.Endpoint
def notify(%{payload: %{id: id, user_id: user_id}, type: :game_left}) do
Endpoint.broadcast!("world:#{id}", "world_left", %{id: user_id})
end
def notify(%{payload: %{from: from, to: to}, type: :ping}) do
Endpoint.broadcast!("user:#{to}", "ping", %{from: from})
end
def notify(message) do
Logger.debug(fn -> "Unknown notification #{inspect(message)}" end)
end
end
I can use this module from my server.
Here is my socket.js file in my phoenix app
import { Socket } from "phoenix"
let socket = new Socket("/socket", { params: { token: window.userToken } })
socket.connect()
let channel = socket.channel("room:lobby", {})
let chatInput = document.querySelector("#chat-input")
let messagesContainer = document.querySelector("#messages")
chatInput.addEventListener("keypress", event => {
if (event.keyCode === 13) {
channel.push("new_msg", { body: chatInput.value })
chatInput.value = ""
}
})
channel.on("new_msg", payload => {
let messageItem = document.createElement("li")
messageItem.innerText = `[${Date()}] ${payload.body}`
messagesContainer.appendChild(messageItem)
})
channel.join()
.receive("ok", resp => { console.log("Joined successfully", resp) })
.receive("error", resp => { console.log("Unable to join", resp) })
export default socket
This is Room Channel handling the topics
defmodule TestWeb.RoomChannel do
use Phoenix.Channel
def join(_name, _message, socket) do
{:ok, %{hey: "there"}, socket}
end
def handle_in("new_msg", %{"body" => body}, socket) do
broadcast!(socket, "new_msg", %{body: body})
{:noreply, socket}
end
end
Right now I can open up multiple pages and chat message and it will show on both browsers. What I want to be able to do is make the function shown in the code below to be able to display that message in my channel:
defmodule Test do
#How could I connect this broadcast function to display this
#string message into my chat room
def broadcast(message) do
IO.puts(message)
end
end
Probably like this… (with the correct alias to your Endpoint)
def broadcast(message) do
Endpoint.broadcast!("room:lobby", "new_msg", %{body: message})
end
The first parameter is the topic, then the message type, then the payload. This correspond to…
let channel = socket.channel("room:lobby", {})
...
channel.on("new_msg", payload => {
let messageItem = document.createElement("li")
messageItem.innerText = `[${Date()}] ${payload.body}`
messagesContainer.appendChild(messageItem)
})
This makes so much more sense, I feel like I was missing a simple step! I do have on error tho, how can I check my correct Alias because when I do call my function Test.broadcast in the iex I am getting this error:
Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Test
Test
iex(2)> Test.broadcast("hello")
** (UndefinedFunctionError) function Test.broadcast/1 is undefined (module Test is not available)
Test.broadcast("hello")
iex(2)>
The alias of the endpoint… Something like YourAppWeb.Endpoint. This module would be in file lib/your_app_web/endpoint.ex
Oh… by the way, test files are not compiled, First they are in test folder, second exs are for elixir script files and are not meant to be compiled.
Rename it to ex, put it in lib and it will be compiled…
Some people add a lib/mock folder, where they put tests/seeds files.
defmodule Test do
def broadcast(message) do
Test.Endpoint.broadcast!("room:lobby", "new_msg", %{body: message})
end
end
I cant seem to find the correct alias. So this file is located in lib/test.ex , which is not located in the lib/test_web folder. I believe I am calling Endpoint the wrong way because it is still saying its undefined, I cant seem to access any file in this module from the IEX , I tried using a simple IO.puts as well and it says the function is defined.
:o Should I change the name from test to practice or something? I just used the name test for example sake!
Well, what is the name You gave to your phoenix application?
I named it test!
try this
$ mix phx.new phanendery
$ cd phanendery
$ cat lib/phanendery_web/endpoint.ex
Should I do this in a new folder?
yes, as a new test project
defmodule PhanenderyWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :phanendery
socket "/socket", PhanenderyWeb.UserSocket,
websocket: true,
longpoll: false
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phx.digest
# when deploying your static files in production.
plug Plug.Static,
at: "/",
from: :phanendery,
gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
end
plug Plug.RequestId
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Phoenix.json_library()
plug Plug.MethodOverride
plug Plug.Head
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
plug Plug.Session,
store: :cookie,
key: "_phanendery_key",
signing_salt: "mvWhNNeR"
plug PhanenderyWeb.Router
end
This was the response in the terminal to that last command
And this is your endpoint module…
The alias You should use is
PhanenderyWeb.Endpoint