Hi, how can I send a broadcast or a message without subscribing in any module, and I can be able to receive this message in all LiveView modules.
To put it simply, I want to say I need to create a notification center and my all LV module are subscribed and receive it’s messages.
I imagine something like this in my mind that every section of my site after does a job send it their message as a notification.
Central and comprehensive notification
it should be noted Im forced not to use something can save my notifications in disk or db.
Sorry my friend, I’m new in this subject. I just need to send a broadcast and get it in every page, how can I do this?
I mean if my user is here in every page of my site!! can see my message in his browser. now my user just can see my message when he is in the page concerned which has subscribe to same module and includes the samehandle_info
for example: I create a post in admin, I have 3 users are in these sections of my site. (1. shop, 2. blog, 3. support). I need all of them see my message that is a new post has been created in Same time!!
So get a single liveview working to show notifications etc (i.e. subscribe to the source of the notifications and update the socket assigns when a notification is received).
Then display that liveview on every page by putting something like
To help you with how to subscribe to the source of notifications etc, we will need more information, but you will almost certainly end up using PubSub somewhere - you can read up on that here: https://hexdocs.pm/phoenix/1.1.3/Phoenix.PubSub.html
I tested it, it works but but there is an error that I couldn’t fix.
if your client are in a page and don’t navigate to other page it is fine but if one of your clients navigate to other pages, it will be broken.
I created a video to explain, please see top right Notif box. I did 2 action { delete and create}
if you saw the video, there are problems, like closing box or doesnt show notification message
my code:
notification LV
defmodule ErfanWeb.Live.Notification do
use Phoenix.LiveView
alias Erfan.Notif
def mount(_params, _session, socket) do
if connected?(socket) do
Erfan.Notif.subscribe()
end
{:ok, assign(socket, notif_status: false)}
end
def render(assigns) do
~L"""
<%= if @notif_status do %>
<section class="container rtl">
<div class="toast fade show notcoustom rtl" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<svg class="bd-placeholder-img rounded " width="20" height="20" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img"><rect fill="#007aff" width="100%" height="100%"></rect></svg>
<strong class="mr-2">پیام فوری</strong>
<small class="mr-auto text-muted text-left"> همین الان </small>
<button phx-click="close-notif" type="button" class="ml-2 mb-1 close mr-3" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="toast-body">
<%= @msg %>
</div>
</div>
</section>
<% end %>
- " "
end
def handle_event("close-notif", _value, socket) do
IO.inspect "close noooooooooootif"
{:noreply, assign(socket, notif_status: false)}
end
def handle_info({Notif, event, msg, status}, socket) do
IO.inspect "noooooooooootif"
{:noreply, assign(socket, notif_status: true, event: event, msg: msg, status: status)}
end
end
and it’s module:
defmodule Erfan.Notif do
@topic inspect(__MODULE__)
def subscribe do
Phoenix.PubSub.subscribe(Erfan.PubSub, @topic)
end
def notify_subscribers(status, msg, event) do
Phoenix.PubSub.broadcast(Erfan.PubSub, @topic, {__MODULE__, event, msg, status})
{event, msg, status}
end
end
one of the file I called it
defmodule ErfanWeb.Live.Categories do
use Phoenix.LiveView
alias Erfan.Admin.CategoryQuery
def mount(_params, _session, socket) do
if connected?(socket), do: CategoryQuery.subscribe()
{:ok, fetch(socket)}
end
def handle_event("delete_category", value, socket) do
Erfan.Notif.notify_subscribers(:ok, "Category has been deleted", [:category, :delete])
CategoryQuery.delete_category(value["category-id"])
{:noreply, socket}
end
and the other where I called with navigate:
def handle_event("save", %{"category_schema" => category_info}, socket) do
case CategoryQuery.create_category(category_info) do
{:ok, resutl} ->
Erfan.Notif.notify_subscribers(:ok, "Category has been created", [:category, :new])
{:noreply,
socket
# |> redirect(to: Routes.live_path(ErfanWeb.Endpoint, Categories))
|> push_redirect(to: Routes.live_path(socket, Categories))
}
{:error, _, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
the first data-phx-session is my notification and the second is my main content. the second one allows is changed. but the first one after changing page it isn’t changed.
it had an error than forced me to put id in my live_render. and now It isn’t locked, every page it refresh’s data-phx-session and cleans last notification.
but there is still a problem, we have no @socket in app.html and I can’t put it just once for now I put that line in all pages I have
I took a look. I think there might be a liveview bug when a liveview is embedded within the layout (i.e. live_render in app.html.leex) - a live_patch causes the event handler to come detached. If you are able to create a minimal case to demonstrate that we can submit a bug report.
When the live_render is added to each of your liveviews it seems to work ok. The notification gets cleared when you move from one page to another - there’s no avoiding that unless you have some kind of store of notifications and their statuses kept separately from the liveviews - it really depends on what user experience you want. Similarly, you won’t get a “category added” notification on the browser tab you added the category from - the notification fires before the liveview is created to “catch” it and display it. Again, you would need some kind of temporary store if you want your users to see this.
I made a couple of minor changes to your notification liveview to allow display of multiple notifications. The list of notifications and logic for closing them could be moved to a separate process to provide the store mentioned above. Let us know your expected flow and we’ll have a go at making it work. I actually need something similar on a project I’m working on now, so the timing is good!
defmodule ErfanWeb.Live.Notification do
use Phoenix.LiveView
alias Erfan.Notif
def mount(_params, _session, socket) do
# if connected?(socket) do
# Erfan.Notif.subscribe()
# end
Erfan.Notif.subscribe()
{:ok, assign(socket, notifs: [], last_id: 0)}
end
def render(assigns) do
~L"""
<section class="container rtl" style="position: absolute; top: 30px; right: 0;">
<%= for notif <- @notifs do %>
<div class="toast fade show rtl" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<svg class="bd-placeholder-img rounded " width="20" height="20" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img"><rect fill="#007aff" width="100%" height="100%"></rect></svg>
<strong class="mr-2">پیام فوری</strong>
<small class="mr-auto text-muted text-left"> همین الان </small>
<button phx-value-id="<%= notif.id %>" phx-click="close-notif" type="button" class="ml-2 mb-1 close mr-3" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="toast-body">
<%= notif.msg %>
</div>
</div>
<% end %>
</section>
"""
end
def handle_event("close-notif", %{"id" => raw_id}, socket) do
{id, ""} = Integer.parse(raw_id)
notifs = Enum.reject(socket.assigns.notifs, &(&1.id == id))
IO.inspect(notifs, label: "close noooootif")
{:noreply, assign(socket, notifs: notifs)}
end
def handle_info({Notif, event, msg, status}, socket) do
id = socket.assigns.last_id + 1
new_notif = %{id: id, msg: msg, event: event, status: status}
notifs = [new_notif | socket.assigns.notifs]
IO.inspect(notifs, label: "new noooootif")
{:noreply, assign(socket, notifs: notifs, last_id: id)}
end
end
Hi my friend, I don’t think to submit a bug because my English is not good I’m not native. some issue I did but I couldn’t understand them what I meant. if you could it would be great.
Thanks for your suggestion, I submitted many commit today and Tomorrow( Iran Time) I will replace your changes on my code and commit again but before it I try to create easy way and do less code more than now.
but for now with my way I said!
it works, but we should repeat this code in every page
yeh, you are right, but I’m creating a video to teach this LiveView in Persian language, and next lesson is genserver and redis, on that time I will store this, thank you for your suggestions.
I refactored my code and I used your code. this project is a small ordering for cafes.
Yes - and I have confirmed that it’s fixed in current github master against the test case. If you update your dependencies to point to the current commit you should be able to remove all the live_render calls from each liveview and have a single call in app.html.leex.
To do this:
Change the liveview entry in mix.exs to {:phoenix_live_view, "~> 0.8.0", github: "phoenixframework/phoenix_live_view"}
Unlock the liveview dependency - command line is mix deps.unlock phoenix_live_view
Update the liveview dependency - mix deps.update phoenix_live_view
Let us know how you go with the genserver version.