defmodule LiveViewBus do @moduledoc """ A simple event bus for Phoenix LiveViews to send events between LiveViews and LiveComponents by ID—without manually wiring PubSub, topics, and `handle_info` every time. ## Topic convention All topics use the prefix `live_view_bus:` to avoid collisions with other PubSub usage. For example, subscribing to ID `"products_list"` subscribes to topic `"live_view_bus:products_list"`. ## Message format Messages are delivered as: {:live_view_bus, id, event, payload} Where: - `id` is the topic ID you subscribed to - `event` is the event name (string) - `payload` is the payload map (defaults to `%{}`) ## Example # In your LiveView's mount: def mount(_params, _session, socket) do LiveViewBus.subscribe(MyApp.PubSub, "products_list") {:ok, assign(socket, products: [])} end # In terminate (optional, Phoenix cleans up subscriptions when process ends): def terminate(_reason, socket) do LiveViewBus.unsubscribe(MyApp.PubSub, "products_list") :ok end # Handle incoming events: def handle_info({:live_view_bus, "products_list", "filters_changed", payload}, socket) do products = load_products(payload) {:noreply, assign(socket, products: products)} end # Send from another LiveView or Component: LiveViewBus.send(MyApp.PubSub, "products_list", "filters_changed", %{ category: "electronics", sort: "price" }) """ @doc """ Returns the PubSub topic for the given ID. """ @spec topic(String.t() | atom()) :: String.t() def topic(id), do: "live_view_bus:#{id}" @doc """ Subscribes the current process to the given ID. The process will receive messages as `{:live_view_bus, id, event, payload}`. ## Examples LiveViewBus.subscribe(MyApp.PubSub, "products_list") LiveViewBus.subscribe(MyApp.PubSub, "dashboard") """ @spec subscribe(module() | pid(), String.t() | atom()) :: :ok | {:error, term()} def subscribe(pubsub, id) do Phoenix.PubSub.subscribe(pubsub, topic(id)) end @doc """ Unsubscribes the current process from the given ID. """ @spec unsubscribe(module() | pid(), String.t() | atom()) :: :ok def unsubscribe(pubsub, id) do Phoenix.PubSub.unsubscribe(pubsub, topic(id)) end @doc """ Sends an event to all processes subscribed to the given ID. ## Examples LiveViewBus.send(MyApp.PubSub, "products_list", "filters_changed", %{ category: "electronics", sort: "price" }) LiveViewBus.send(MyApp.PubSub, "dashboard", "refresh") """ @spec send(module() | pid(), String.t() | atom(), String.t() | atom(), map()) :: :ok def send(pubsub, id, event, payload \\ %{}) do broadcast(pubsub, id, event, payload) end @doc """ Broadcasts an event to all processes subscribed to the given topic ID. Same as `send/4` but with an explicit name for "broadcast to topic" semantics. Use this when you want to send to a group topic (e.g. `"room_123"`). ## Examples LiveViewBus.broadcast(MyApp.PubSub, "dashboard", "data_refreshed", %{ timestamp: DateTime.utc_now() }) """ @spec broadcast(module() | pid(), String.t() | atom(), String.t() | atom(), map()) :: :ok def broadcast(pubsub, id, event, payload \\ %{}) do message = {:live_view_bus, id, event, payload} Phoenix.PubSub.broadcast(pubsub, topic(id), message) :ok end end