grych / drab

Remote controlled frontend framework for Phoenix.

Home Page:https://tg.pl/drab

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Drab.Push ?

guidotripaldi opened this issue · comments

Until now Drab is the way to react, from the server side, to events generated from the browser side.

But what if we need to trigger the update of the client browser after an event generated from the server side, for example by some other processes of our application (i.e. a database update, etc)?

I think we need a new module (e.g. Drab.Push) that allows a Commander to register an handler tied with a topic (a sort of inter-processes channel), and that allows other processes to push notifications/updates for that topic, something like that:

defmodule Drab.Push do

  def register(socket, topic, handler) do
    # store somehow and somewhere, a reference for the caller socket and the handler, 
    #  bound to the given topic
  end
  
  def push(data, topic) do
   # push data to every client registered to a certain topic
    clients(topic)
    |> Enum.map(fn client ->
           call_client_handler(client, data)
         end)
  end
  
  defp clients(topic) do
    # retrieve all registered clients for a certain topic
  end

  defp call_client_handler(client, data) do
    {socket, client_handler} = client
     client_handler(socket, data)
  end

end
# in a Commander

def register_my_update(socket) do
  topic = "foo"
  handler = &MyModule.my_update/2
  Drab.Push.register(socket, topic, handler)
end

def my_update(socket, data) do
  case data do
   {:ok, value} -> poke(socket, my_assign: value)
   {:error, error} -> ...
  end
end
# somewhere in other modules of the App

def do_update_database() do
   ...
   |> Repo.update()
   |> Drab.Push.push("foo")
end

What is the benefit of this comparing to standard broadcasting functions? You can call broadcast_js from any part of the app, and you can use a topic:

def do_update_database() do
   ...
   |> Repo.update()
   |> Drab.Core.broadcast_js(same_topic("foo"), "alert('foo');")
end

because, afaik,

  • the topic issued with Drab.Commander.broadcasting/1 is static (you have to hard code it in the Commander). Instead the suggested method lets dynamically define multiple topics during runtime for the same Commander;
  • broadcast_js just executes javascript, doesn't poke assignments;
  • the (future) version of broadcast_poke that will accept a subject suffers the same above problem

the topic issued with Drab.Commander.broadcasting/1 is static (you have to hard code it in the Commander). Instead the suggested method lets dynamically define multiple topics during runtime for the same Commander;

Yes, I've been thinking about it. The issue is I have no idea yet how to do it :-)

broadcast_js just executes javascript, doesn't poke assignments;

Sure, this is what broadcast_poke will be when fixed

the (future) version of broadcast_poke that will accept a subject suffers the same above problem

No. Broadcast_poke will broadcast the assign value as well.

No. Broadcast_poke will broadcast the assign value as well.

I mean the problem that its subject/topic is static, as it is issued with Drab.Commander.broadcasting/1

Yes, I've been thinking about it. The issue is I have no idea yet how to do it :-)

Maybe the solutions is similar to mine proposal: dynamically register the socket bound to the given topics

In my current toy implementation of Drab.Push, I'm using a quite standard genServer.cast/call handle_cast/handle_call to store the socket/topic tuple. It seems to works well, but I'm mostly concerned regarding:

  • how to recognize when a client becomes obsolete, to pop off it from the stack?
  • how about memory and performances when a lot of clients register?
  • To use or not to use Phoenix Channels as the underline protocol?

so before continue on my own, I like to understand if the proposed Drab.Push could be a solution for the current limitation of static topics, and in that case reasoning together on which are the best strategy and implementation

Maybe the solutions is similar to mine proposal: dynamically register the socket bound to the given topics

I don't like keeping the list of hundred of thousands of sockets, handling disconnections etc etc. I don't feel strong enough to rewrite Phoenix Channel ;)

I don't like keeping the list of hundred of thousands of sockets, handling disconnections etc etc. I don't feel strong enough to rewrite Phoenix Channel ;)

yes! Exactly my same concerns!

We will need to find a way how to do a dynamic approach with Channel way.

Closing this topic as it might be confusing.

Given the needs to be able to update browser when new data is available on server but for only a subset of the connected users on the same page (those for whom the specified topic is pertinent), I'm wondering if is there a way to trigger a drab function from the Commander as if it were clicked from the browser. I mean, in the commander call broadcast_js to ask the browser of all connected users to execute a specified drab function from their sides, for example:

# in the Commander

@doc """
Request the connected browsers to update their page with data peculiar to a certain topic
"""
def topic_needs_update(subject, topic)
   broadcast_drab_trigger(subject, "update_topic", topic)
end

@doc """
Request the connected browsers to trigger the specified drab function handler 
"""
def broadcast_drab_trigger(subject, function, value) do
   broadcast_js subject, "callDrab(#{function}, #{value})"
end

@doc """
When server-side triggered, browsers will call this handler that customize the new data for each user
"""
defhandler update_topic(socket, sender, topic) do
   current_user  = peek socket, :current_user
   new_data = compute_new_data(current_user , topic)
   poke socket, data: new_data
end
<!-- Somewhere on the page -->

<script>
  function callDrab(functionName, value) {
   // how to trigger a drab function this way?
   }
</script>

In brief:

  1. server: there is new data for a certain topic
  2. server: broadcast to connected users that they have to call an handler
  3. browser: the specified drab handler is called
  4. server: the called handler elaborates the data depending on the peculiar combination of topic/current user (the topic is passed as the third value to the handler, the user is extracted from the incoming socket), for each caller
  5. server: updates the browser with the new values

Certainly this is not the most efficient and elegant way, but it could be a workaround to let updates based on topics/user combination.

Well, this is complicated. We still need to find a way how to sign to the broadcasting subject in runtime. It should be a part of 1.0, I will try to figure it out after I launch 0.9

// how to trigger a drab function this way?
use Drab.exec_elixir()

oh, I had completely miss it reading the docs, thanks!

I've just pushed the commit for #148. This is probably exactly what you were looking for: subscribe to topics in a runtime.