Drab: remote controlled frontend framework for Phoenix



Lol, well the PR has nothing to do with 1.3 stuff, that is just release stuff. Calling Code.ensure_compiled or whatever it was returns {:error, :embedded} when the program is mix released, thus crash only on release. ^.^


OK, I will do smaller release than I planned especially for you :slight_smile:
Anyway, it will contain P13 fix.


Lol, thanks! It is quite a blocking bug considering mix release fails otherwise. ^.^;


Thanks for your both answers !
Then I will keep some templates with plain html, thats ok (still I think that would be great to manage Haml ! :wink:

I didn’t get at first Drab’s ability to call functions directly from js ; that’s cool !

My plan is to place some assigns in an html template, play with it with js, then push some changes to repo and load fresh new assign in the template (without reloading the whole page). I like Drab’s approach to avoid Ajax, and tried to use put_store and storage location on load. My point is I can’t find how to make that work :

  • My commander doesn’t look to see my controller’s assigns (storage location remains empty)
  • I manage to query Ecto directly from the commander, but get an “Assign not found in Drab EEx template” (and i am not sure that querying Ecto should be done by Drab…)

Any suggestion ?
Txs !


Are you using peek/2 to retrieve the assign value?
About Ecto: Assign not found doesn’t have anything with Ecto. You can use Ecto queries anywhere, of course also in the Drab commander.

Please share a part of your code, we will find out how to manage it.


Here it is (I simplified a bit) :

My membership’s controller has a test method :

def test(conn, _params) do
  memberships =
    query_user(conn, _params)
    |> Repo.all
    |> assign(:memberships, memberships)
    |> render "test.html"

The membership’s commander :

def page_loaded(socket) do
  initial = peek(socket, :memberships) # Load assign
  put_store(socket, :memberships, initial) # Store assign in localStorage
  current = get_store(socket, :memberships) # Retrieve assign from localStorage

I do not render the @memberships in the template, but use some js afterward to check localStorage content. Still no magic as I keep getting an empty localStorage and a :

 Assign @memberships not found in Drab EEx template 

Does it mean the assign must be rendered ? Where am I wrong ?


Store (and session) is the different entity than assigns.

With Drab, you use assigns normally, while rendering template from the controller. Then, you may poke and peek assigns live, from the commander.

All is explain on the demo/tutorial page, here https://tg.pl/drab#poke


Ok, I realized I misunderstood when to use Drab (I was thinking Drab’s store is accessible from js thanks to localStorage By default, Drab Store is kept in browser Local Storage., but cant find how).

I turned tmyself to PhoenixGon to load assigns in js, then I will use Drab to trigger updates in my templates.

Thanks again for your help


Drab Store is kept in the local store, or memory (as configured) and it is available only from Drab. Well, in theory you could decrypt it in JS (values are signed with Phoenix.Token), but the intention is to keep all the code on the server side.


@grych Drab is a really cool library! Thanks for making it. Lately I’ve been feeling like building SPA’s presents a lot of unnecessary complexity for so little extra benefit. Your library can fill the gap.

I haven’t dug too deeply into Drab (looked through the README and hexdocs) but have a question. I sometimes use Postgres listen/notify to get real time updates when data changes. I’ve used the Boltun library to manage listen/notify callbacks and am wondering if it is possible to trigger a commander function from another module on the server (e.g. from my Boltun callback). I’d like to update a table of records on all connected clients via drab when the database is changed. Sorry if this is documented somewhere and I missed it :grimacing:


You just need to broadcast the update to all connected browsers. You can do it from any part of your application, as broadcasting functions do not need a socket to send the updates.

You may use Drab.Element.broadcast_prop or the similar function from Drab.Query or even simple Drab.Core.broadcast_js

for the broadcasting options.

BTW docs about broadcasting should be updated, not easy to find it.

BTW2 do you think I should make a broadcast versions of Drab.Element.set_attr, set_style etc? For now there is only broadcasting version of set_prop.


I’m having trouble getting it to work. I have a controller (ScrivWeb.CaseController) that is using Drab.Controller. I also have a CaseCommander module. I have a couple commander functions that work fine so I know it is setup correctly. The issue arises when in CaseController I call

Drab.Core.broadcast_js(Drab.Core.same_controller(ScrivWeb.CaseController), "alert('Broadcased')")

I’m calling it after the create action creates a new case in the controller to test that I can send a message to all connected clients. I get the following error:

[error] #PID<0.936.0> running ScrivWeb.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /cases
** (exit) an exception was raised:
    ** (ArgumentError) argument error
        (stdlib) :ets.lookup(ScrivWeb.PubSub, :broadcast)
        (phoenix_pubsub) lib/phoenix/pubsub.ex:288: Phoenix.PubSub.call/3
        (drab) lib/drab/core.ex:248: Drab.Core.broadcast_js/3
        (scriv) lib/scriv_web/controllers/case_controller.ex:30: ScrivWeb.CaseController.create/2
        (scriv) lib/scriv_web/controllers/case_controller.ex:1: ScrivWeb.CaseController.action/2
        (scriv) lib/scriv_web/controllers/case_controller.ex:1: ScrivWeb.CaseController.phoenix_controller_pipeline/2
        (scriv) lib/scriv_web/endpoint.ex:1: ScrivWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (scriv) lib/scriv_web/endpoint.ex:1: ScrivWeb.Endpoint.plug_builder_call/2
        (scriv) lib/plug/debugger.ex:99: ScrivWeb.Endpoint."call (overridable 3)"/2
        (scriv) lib/scriv_web/endpoint.ex:1: ScrivWeb.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) /Users/ryanswapp/Documents/code/elixir/test-apps/scriv/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

Any idea as to what I may be doing wrong?

Also, is it possible to only send a message to clients connected to the page rendered by the index action instead of all clients connected to a page rendered by any action in the CaseController?

Thanks for your help :slight_smile:

Edit: I realized that I need to pass the socket struct to the broadcast_js function rather than the string returned from same_controller(). I’m guessing that socket struct is stored somewhere I can access so I’ll look around the code to find out where you are putting it.


Does this command work from IEx, when you have a page displayed in the browser?

:ets.lookup(ScrivWeb.PubSub, :broadcast) looks nasty here.


For broadcasting, you don’t need a socket. Take a look at the presence example:


Yes, use broadcasting :same_path option.


@ryanswapp , could you please try to get https://github.com/grych/drab-example and run:

Drab.Core.broadcast_js Drab.Core.same_path("/"), "alert('a')"

on that?
So we would know if there is an issue with the environment or with the setup.


No it does not :frowning:

iex(2)> :ets.lookup(ScrivWeb.PubSub, :broadcast)
** (ArgumentError) argument error
    (stdlib) :ets.lookup(ScrivWeb.PubSub, :broadcast)

Drab.Core.broadcast_js Drab.Core.same_path("/"), "alert('a')"

This works in your example. I cloned it and everything worked fine. I’m not sure why, but perhaps there is an issue with Phoenix 1.3 (I’m on Drab 0.5 and I saw an issue stating 1.3 wasn’t supported)? My test app is on 1.3.

With regard to setup, I’ve followed the README instructions exactly and just double checked to make sure everything was there. I set it up to do the uppercase example and it all works fine. The code fails, however, when I try to execute stuff in iex.


@ryanswapp, could you please check it with github’s master? I hope the issue is resolved.


Just upgraded to master branch and it works! Thanks :slight_smile: