Skip to content

Latest commit

 

History

History
181 lines (153 loc) · 5.03 KB

README.md

File metadata and controls

181 lines (153 loc) · 5.03 KB

PhoenixTurbo

Use Turbo in your Phoenix app. Sample App

Installation

  1. Add PhoenixTurbo to your list of dependencies in mix.exs:
def deps do
  [
    {:phoenix_turbo, "~> 0.1.0"}
  ]
end
  1. Install Turbo
  2. Install Stimulus (Optional)

Setup

in lib/[my_app]_web.ex

   @doc """
   When used, dispatch to the appropriate controller/view/etc.
   """
   defmacro __using__(which) when is_atom(which) do
-    apply(__MODULE__, which, [])
+    quote do
+      unquote(apply(__MODULE__, which, []))
+      unquote(apply(PhoenixTurbo, which, []))
+    end
   end
 end

Configure new mime type in config/config.exs

+  config :mime, :types, %{
+    "text/vnd.turbo-stream.html" => ["turbo-html"]
+  }

And recompile the mime plug after that

  mix deps.clean mime --build
  mix deps.get

in lib/[my_app]_web/endpoint.ex

 defmodule MyAppWeb.Endpoint do
   use Phoenix.Endpoint, otp_app: :chat
+  use PhoenixTurbo.StreamHelper

in lib/[my_app]_web/channels/user_socket.ex

 defmodule MyAppWeb.UserSocket do
   use Phoenix.Socket

+  channel "turbo-streams:*", PhoenixTurbo.Channel
+
   ## Channels
   # channel "room:*", ChatWeb.RoomChannel

in lib/[my_app]_web/router.ex

   pipeline :browser do
+    plug :accepts, ["html", "turbo-html"]
     plug :fetch_session
     plug :fetch_flash
     plug :protect_from_forgery
     plug :put_secure_browser_headers
+    plug :handle_turbo_frame
   end

Add javascript file:

// cable_stream_source_element.js
import { connectStreamSource, disconnectStreamSource } from "@hotwired/turbo"
import socket from "./socket"

class TurboCableStreamSourceElement extends HTMLElement {
  connectedCallback() {
    connectStreamSource(this)
    const channelName = this.getAttribute("channel")
    const sign = this.getAttribute("signed-stream-name")
    this.channel = socket.channel(`turbo-streams:${channelName}`, { sign })
    this.channel.join()
    this.channel.on("update_stream", ({ data }) => {
      this.dispatchMessageEvent(data)
    })
  }

  disconnectedCallback() {
    disconnectStreamSource(this)
    if (this.channel) this.channel.leave()
  }

  dispatchMessageEvent(data) {
    const event = new MessageEvent("message", { data })
    return this.dispatchEvent(event)
  }
}

customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement)

Usage:

Controller helpers

  1. turbo_stream_request?/1: Return true if it's a "turbo-stream" request.
  2. render_turbo_stream: Render turbo_stream template. The same effect with Phoenix.Controller.render/3 except subtracting layout and putting turbo stream content type to the HTTP response content type.
defmodule ChatWeb.MessageController do
  use ChatWeb, :controller

  alias Chat.Rooms
  alias Chat.Rooms.Message

  plug :set_room

  def create(conn, %{"message" => message_params, "room_id" => room_id}) do
    message = Rooms.create_message!(Map.put(message_params, "room_id", room.id))

    if turbo_stream_request?(conn) do                                         # <- turbo_stream_request?
      render_turbo_stream(conn, "create_turbo_stream.html", message: message) # <- render_turbo_stream
    else
      redirect(conn, to: Routes.room_path(conn, :show, room_id))
    end
  end
end

View helpers

turbo_stream_tag: <%= turbo_stream_tag @conn, @post %> would generate

<turbo-cable-stream-source channel="posts_1" signed-stream-name="xxxxx"></turbo-cable-stream-source>

which will work with cable_stream_source_element.js

Others

MyApp.Endpoint.update_stream: Send stream updates via websocket.

defmodule ChatWeb.MessageController do
  use ChatWeb, :controller

  alias Chat.Rooms
  alias Chat.Rooms.Message

  plug :set_room

  def create(conn, %{"message" => message_params}) do
    room = conn.assigns.room
    message = Rooms.create_message!(Map.put(message_params, "room_id", room.id))

    if turbo_stream_request?(conn) do
      ChatWeb.Endpoint.update_stream(               # <-- send stream updates
        room,                                       # <-- send stream updates
        ChatWeb.MessageView,                        # <-- send stream updates
        "create_turbo_stream.html",                 # <-- send stream updates
        message: message                            # <-- send stream updates
      )                                             # <-- send stream updates
      send_resp(conn, 201, message.content)
    else
      redirect(conn, to: Routes.room_path(conn, :show, conn.assigns.room))
    end
  end

  defp set_room(conn, _) do
    room_id = conn.params["room_id"]
    room = Rooms.get_room!(room_id)
    assign(conn, :room, room)
  end
end

References