Created
October 28, 2025 22:00
-
-
Save mcrumm/24f353b9e81a7aeb9356a270d86882e6 to your computer and use it in GitHub Desktop.
Ticking apparatus for Phoenix LiveView.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| defmodule Ticking do | |
| @moduledoc ~S''' | |
| Ticking apparatus for Phoenix LiveView. | |
| This version is mainly for testing LiveView operations that may be affected by change tracking. | |
| ## Usage | |
| In your LiveView module, add `use Ticking`. Then add the `@ticking` assigns in your template to | |
| force a page update on the tick interval. | |
| ```elixir | |
| defmodule MyLive.Example do | |
| use Phoenix.LiveView | |
| use Ticking | |
| def render(assigns) do | |
| ~H""" | |
| <p>Hello, world! Ticking time: <%= @ticking %></p> | |
| """ | |
| end | |
| end | |
| ``` | |
| ### Custom Timing | |
| The default interval is `5_000` milliseconds and can be modified using the `:time` option: | |
| ```elixir | |
| defmodule MyLive.FastExample do | |
| use Phoenix.LiveView | |
| use Ticking, time: 500 | |
| def render(assigns) do | |
| ~H""" | |
| <p>I tick fast: <%= @ticking %></p> | |
| """ | |
| end | |
| end | |
| ''' | |
| defmacro __using__(opts \\ []) do | |
| time = Keyword.get(opts, :time, 5_000) | |
| quote do | |
| require Phoenix.LiveView | |
| Phoenix.LiveView.on_mount({Ticking, unquote(time)}) | |
| def handle_info({__MODULE__, Ticking}, socket) do | |
| {:noreply, Ticking.update(socket, unquote(time))} | |
| end | |
| end | |
| end | |
| alias Phoenix.Component | |
| alias Phoenix.LiveView.Socket | |
| def on_mount(time, _params, _session, socket) do | |
| socket = | |
| socket | |
| |> Component.assign(ticking: nil, ticking_ref: nil) | |
| |> Phoenix.LiveView.put_private(Ticking, time) | |
| |> Phoenix.LiveView.attach_hook(Ticking, :handle_params, &Ticking.on_handle_params/3) | |
| {:cont, socket} | |
| end | |
| def on_handle_params(_params, _uri, socket) do | |
| {:cont, Ticking.update(socket, ticking_time(socket))} | |
| end | |
| def cancel(%Socket{} = socket) do | |
| update(socket, false) | |
| end | |
| def update(%Socket{} = socket, time \\ 5_000) do | |
| socket | |
| |> Component.update(:ticking, fn _ -> System.system_time() end) | |
| |> Component.update(:ticking_ref, fn ref -> | |
| if ref, do: Process.cancel_timer(ref) | |
| if time, do: Process.send_after(socket.root_pid, {socket.view, Ticking}, time) | |
| end) | |
| end | |
| defp ticking_time(%Socket{} = socket) do | |
| Map.fetch!(socket.private, Ticking) | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment