Looking to create a simple function in Phoenix

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.

1 Like

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 :slight_smile: Enjoy the challenge!

1 Like

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?

Also this https://elixirgirls.com/guides/channels.html

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.

1 Like

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

1 Like
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