Last active
June 7, 2023 13:27
-
-
Save conradfr/50c250ea5fddcf12979f6ca5282a3af1 to your computer and use it in GitHub Desktop.
LiveView Boostrap v5 toast LiveView
This file contains 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
One possible implementation of Bootstrap toasts: | |
Against just making it a Live Component (see gist: https://gist.github.com/conradfr/a2f072edfb889bfd169bed22421e2ac6) | |
- Pros : You can ask for a toast to show and navigate to another LiveView, the toast is then able to be displayed without being unmouted by the navigation and therefore not showing | |
- Cons: Makes it harder to communicate between the LiveView and your toast container. | |
1/ The toast container is a separate LiveView from your "main" LiveView | |
2/ A unique id (page_id) is generated by the javascript and transmitted to all LiveViews. | |
3/ The Toast LiveView register itself to a registry using page_id | |
4/ The regular LiveView uses page_id and the registry to send a message to the Toast LiveView. | |
For how to use, check: | |
- example_from_another_hook.js | |
- example.ex |
This file contains 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
const Hooks = {}; | |
Hooks.BsToast = BsToastHook; | |
let liveSocket = new LiveSocket('/live', Socket, { | |
hooks: Hooks, | |
params: { | |
page_id: Math?.floor(Math.random() * 100000000000000000000) | |
} | |
}); |
This file contains 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
# add to children in start/2 | |
{Registry, [keys: :unique, name: MyAppApiRegistry]} |
This file contains 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 MyAppWeb.BsToastLive do | |
use MyAppWeb, :live_view | |
@status [:success, :error] | |
@impl true | |
def render(assigns) do | |
~H""" | |
<div | |
id="bs-toast-container" | |
phx-hook="BsToast" | |
phx-update="stream" | |
class="toast-container position-absolute end-0 p-3" | |
> | |
<div | |
:for={{id, toast} <- @streams.toasts} | |
id={id} | |
role="alert" | |
aria-live="assertive" | |
aria-atomic="true" | |
class={"toast text-white #{if toast.type == :success, do: "bg-success"}#{if toast.type == :error, do: "bg-warning"}"} | |
> | |
<div class="d-flex justify-content-center align-items-center p-3"> | |
<div class="toast-icon"> | |
<i class={"bi #{if toast.type == :success, do: "bi-check-circle-fill"}#{if toast.type == :error, do: "bi-x-circle-fill"}"}> | |
</i> | |
</div> | |
<div class="toast-body flex-fill py-0 text-center align-middle"> | |
<%= toast.message %> | |
</div> | |
</div> | |
</div> | |
</div> | |
""" | |
end | |
@impl true | |
def mount(_params, _session, socket) do | |
if connected?(socket) do | |
process_name = get_toast_process_name(socket) | |
Registry.register(MyAppRegistry, process_name, :toast) | |
end | |
{:ok, | |
socket | |
|> stream(:toasts, []), layout: false} | |
end | |
@impl true | |
def handle_info({:display_toast, message, status}, socket) when is_binary(message) and status in @status do | |
toast = Toast.new(message, status) | |
{:noreply, | |
socket | |
|> stream_insert(:toasts, toast) | |
|> push_event("show_toast", %{id: "toasts-" <> toast.id})} | |
end | |
@impl true | |
def handle_info({:display_toast, _message, _status}, socket) do | |
{:noreply, socket} | |
end | |
@impl true | |
def handle_event("toast_closed", %{"id" => id} = _params, socket) do | |
{:noreply, stream_delete_by_dom_id(socket, :toasts, id)} | |
end | |
# we get the unique id from the app.js so both liveviews have the same | |
defp get_toast_process_name(socket) do | |
"bs_toast_" <> get_page_id_from_socket(socket) | |
end | |
def get_page_id_from_socket(socket) do | |
with %{} = params <- Phoenix.LiveView.get_connect_params(socket) do | |
params | |
|> Map.get("page_id") | |
|> Integer.to_string() | |
else | |
_ -> nil | |
end | |
end | |
end |
This file contains 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
const TOAST_DURATION = 3500; | |
const BsToastHook = { | |
mounted() { | |
this.handleEvent('show_toast', ({ id }) => { | |
const toastElem = document.getElementById(id); | |
if (toastElem) { | |
const toast = new bootstrap.Toast(toastElem, {delay: TOAST_DURATION}); | |
toast.show(); | |
setTimeout( | |
() => { | |
toast.hide(); | |
this.pushEventTo(this.el, 'toast_closed', { id }); | |
}, | |
TOAST_DURATION + 500 | |
); | |
} | |
}); | |
} | |
}; | |
export default BsToastHook; |
This file contains 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 MyAppWeb.ExampleLive do | |
use MyAppWeb, :live_view | |
@impl true | |
def render(assigns) do | |
~H""" | |
<div> | |
<button phx-click="send_toast">Send toast</button> | |
</div> | |
""" | |
end | |
@impl true | |
def mount(_params, _session, socket) do | |
{:ok, | |
socket | |
|> assign( | |
page_id: get_page_id_from_socket(socket) | |
) | |
end | |
@impl true | |
def handle_event("send_toast", _params, socket) do | |
with pid when pid != nil <- get_pid_of_toast_lv(socket.assigns.page_id) do | |
Process.send(pid, {:display_toast, "Great Toast !", :success}, []) | |
else | |
_ -> :error | |
end | |
{:noreply, socket} | |
end | |
def get_pid_of_toast_lv(id) do | |
case Registry.lookup(MyAppRegistry, "bs_toast_" <> id) do | |
[] -> nil | |
[{pid, _}] -> pid | |
end | |
end | |
end |
This file contains 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
const AnotherHook = { | |
mounted() { | |
this.pushEventTo('#bs-toast-container', 'display_toast', { status: 'success', message: 'I love toast!' }); | |
} | |
} |
This file contains 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
<%= live_render(@conn, MyAppWeb.BsToastLive) %> |
This file contains 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 Toast do | |
use Ecto.Schema | |
@primary_key false | |
embedded_schema do | |
field(:id, :string) | |
field(:message, :string) | |
field(:type, Ecto.Enum, values: [:success, :error]) | |
end | |
def new(message, type \\ :success) when is_binary(message) do | |
%Toast{ | |
id: System.unique_integer([:positive]) |> Integer.to_string(), | |
message: message, | |
type: type | |
} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment