Drab: remote controlled frontend framework for Phoenix



There are new examples on the demo page:


Heh, the issue with unpoly is it does not only DOM-element swapping but also URL changing without reloading the DOM. ^.^;

It makes the concept of a per-page commander kind of breaking without some way to re-establish their connections, as it is if you want to mix unpoly and drab you need to use no per-page commanders and instead use purely only shared commanders, which is doable, just have to design with that in mind. :slight_smile:


I’m curious, does it load it up from DETS then persist it? Or does it access it from DETS on every request (much much slower)? And instead of DETS why not instead just bake it into a module? :slight_smile:

Whooo! This will make compositing templates so much nicer! :slight_smile:

Very useful! Can have far more shared code now without needing to make more round-trips to the client to get extra information! :slight_smile:



I see. Anyway, I am not planning to make a module for any JS library in the nearest future, I will rather concentrate on the Drab core.

It is a genserver loading the cache on start.
I choose DETS on the early experimental stage :wink: it was easiest for me to build it and debug. And yes, I am planning to put it into module, like Phoenix.View does. But so far it works, so it can wait :slight_smile:

Still, shared commanders have limitations comparing to the ordinary commanders. For example, callbacks and access to the session are not working there. And this is a question: shall shared commanders behave like full commanders, with callbacks, etc? There will be a performance overhead.


Honestly I’d probably just remove the page-specific commanders and instead build the system around the ‘shared’ commander model and optimize around that case.


It could be worse :slight_smile: the original idea was to put event handlers into controllers.

Well, I like the idea of pairs controller/commander. It fits and extends Phoenix, it is secure (only page from the specific controller may run handler in the corresponding commander) and easy to understand. So I will keep it.

But… why not to do both? If the corresponding commander would not be a requirement, and shared commanders would have all the powers of ordinary commanders (callbacks etc), you could choose the way you want to use it.


You could, but think about it from a page decorated with unpoly, a couple of different phoenix-side pages/controllers could be being displayed all at the same time. Thus which page Commander do you use? If you use all then how do they know only to control the parts of the DOM that they know about and not mess up other parts? If you use only one then potentially large amounts of the page will be non-functional. Etc… etc… :slight_smile:

That is why I eschew page commanders and only use shared, because the second I add a single partial fragment decoration from unpoly then the page just out-right breaks… ^.^;

Except what is to keep javascript from building that all up itself and pretending to be that page using information acquired earlier? That is why Phoenix websockets don’t handle headers and other such things, because if you have a socket open with whatever access, then you absolutely can NOT know what is opening it or what information they have, thus the concept of page-only accesses makes no sense, only role/permission based accesses based on the arguments passed to the websocket itself, not the page.

Yes I think it’s a failing in the W3 websocket design, but it would have been hard to let it be as functional otherwise.


Does that mean there is currently a security breach as it works today ?


In that model, there will be no one main Page Commander. Only shared.

We may allow shared commander to modify only the parts under <tag drab-commander='...'>

This is what I meant by writing “why not do both”. You may use only shared commander, I can choose classic way.

Nothing - if you keep the websocket connection opened. PID of the Drab genserver is in the token (to route the request to the correct one), so you can’t just reuse the old token.

But, I can imagine the situation, when you acquire enough tokens, and the beam was restarted for some reasons so it is start using small PIDs, for which you already have tokens. You may try, and with some luck find actually running one.
I am going to prevent reusing tokens by adding additional random key generated on application start and kept in memory only. (https://github.com/grych/drab/issues/81)

Actually Drab keeps the controller information in the token, and check if you are running commander generated by the corresponding controller.
Obviously it does not work with shared commanders :slight_smile:


Nothing what I am aware of.


That is what I would expect. :slight_smile:

Except some malicious bot can just open a few pages, scrape the tokens, and connect with all it wants to do. ^.^

/me constantly tries to think of how to break sites…

/me still wishes that phoenix websockets passed the headers on, would love to filter connections based on IP for example, that’s still a bit of a wtf in phoenix to them…


We will prevent it with https://github.com/grych/drab/issues/81


Except of course they could just scrape a page every time they want to hit the websocket again. :wink:

Websocket’s security is far different from pages. :slight_smile:
It’s not really page based or even page-safe, it’s only permissions/token safe (assuming the permissions and tokens are safe to begin with, I.E. a page-specific token is not really).


Sure. But they need to keep the scraper connected, if they want to reuse the token. It is going to be invalid after disconnection. Not very handy method of attack :wink:


It’s quite fine if they are probing all the things they can access. ^.^


Got your point. Yes, you may open the browser, keep it connected and just try to run random handler names, using the common name like “button_clicked” or “Overmind.generate_form”, etc, to find out the function which is a Drab Handler (it is in the connected commander, or it is public in the shared one).

Now I am thinking about whitelisting handlers also in the “standard” commanders. Now you need to keep in mind that public functions there are actually handlers, all of them (with arity of 2 or 3). It is mentioned in the docs, but who reads the docs those days :wink:


Hi Grych

I had some trouble using Drab on a very simple case : I just need to toogle a button and change its format depending on Drap’s. Since I render this button n times on the page, I couldn’t use Drap assign. My point is in the Drab.Element module functions answer tupples like {:ok, 3} which makes it hard to use in a pipe.

I ended up with this (which I am not very proud of).
Any suggestion on how better I could do ?
Is there a way to render a partial for this button from Drab ?

case Contents.toogle_pack_item(item_id, pack_id) do
  {:created, pack_item} ->
      |> Drab.Browser.console("PackItem created for item #{item_id} and pack #{pack_id}")
      |> set_attr("a.item[id='#{item_id}']", class: "item waves-effect waves-light btn validate")
      |> set_prop("a.item[id='#{item_id}']", innerHTML: "Remove")
      |> insert_html("a.item[id='#{item_id}']", :beforeend, "<i class='toogle_btn material-icons left'>remove_circle_outline</i>")
  {:deleted, pack_item} ->
      |> Drab.Browser.console("PackItem removed for item #{item_id} and pack #{pack_id}")
      |> set_attr("a.item[id='#{item_id}']", class: "item waves-effect waves-light btn light")
      |> set_prop("a.item[id='#{item_id}']", innerHTML: "Add")
      |> insert_html("a.item[id='#{item_id}']", :beforeend, "<i class='toogle_btn material-icons left'>add_circle_outline</i>")
  {:error, _} ->
    |> Drab.Browser.console("Couldn't create PackItem between item #{item_id} and pack #{pack_id}")


Hi @thib,
it looks OK.
Drabs API is a little bit chaotic now, as some of bang functions returns sockets (so they could be stacked), and some not :wink: it must be changed before going stable. Also, there is a plan to group instructions like this (series of set_prop, set_attr, etc) into one, so behind the scenes it would not send this instructions one by one (and wait for the answer!), but in one bunch.

About the partials, you may use it with Drab! Take a look here. I need to add an example of this feature on the demo page.


Hi folks,
quick update about what’s cooking in the Drab kettle.

The next big release will be about Commanders and there will be changes in the API. Sorry for this, but I believe it makes sense. Please comment.

  • use Drab.Controller is going to be optional, if the corresponding commander exists

  • all event handlers, even those in the “normal” commander, must be declared as handlers; and instead of using existing public [:handler1, :handler2] macro, I am thinking about something like defhandler clicked(socket, payload). This is because I want to make sure when you are writing a public function in the commander, you are aware that it could be run from the client side!

  • shared commanders must behave like the standard commanders, so all callbacks must work

  • for the above (for onload, onconnect and ondisconnect), Drab must somehow know which commanders are used on the page. I could read it on the client side, but I do not consider it safe. For now I think I’m gonna force to declare shared commanders with use Drab.Controller, commanders: [Shared1, Shared2] in the controller, which generated the page.

  • when using shared commander inside the <tag drab-commander=MyCommander>, all the reads and updates are going to be done only inside this tag.


Hello, I am using drab to update queues on server.
In my app, i call poke frequently, and it worked perfectly :).
but i just wonder if there is any way to disable logs.

  defp monitor_exq(socket) do
    processes = Sally.Utils.Exq.current_processes()
    jobs = Sally.Utils.Exq.current_jobs()

    poke socket, processes: processes, jobs: jobs



[debug] INCOMING "event" on "__drab:same_path:/admin" to Drab.Channel
  Transport:  Phoenix.Transports.WebSocket
  Parameters: %{"event_handler_function" => "Drab.Live.Commander.save_assigns", "payload" => %{"__assigns" => %{"gi2tombvguydmnrs" => %{"jobs" => "QTEyOEdDTQ.8ZgkqtnP4eel91aB7QlQB_hDn4XxIgH0X_TeSclbEyFGcEfc4Vfs-UTJd3M.nVU40XJ4mpIPpb9d.Ct_sPkU-pVNrIUo6iB0TqbRul3IvtosrV1KIng.N6bBncQ-51JhWUlUtRe_Rg", "processes" => "QTEyOEdDTQ.8N6RjPO91gN4DcUg-6nDapu4vOoQAy6UXcQVuI0-Y151g9amiPMV_auNOOQ.kvtCWx8kwfyVEl4T.iehsOHUn.Gmvf8LpDDfpRyrfi-ZpZ3Q"}}, "__index" => "gi2tombvguydmnrs"}, "reply_to" => "e78b550d-bd72-465e-9df3-4a7d8579cad2"}


Not currently, but it is a way to pass the logging options to Phoenix.Channel. Could you please create a github issue with this, so it won’t be forgotten? :slight_smile: