Drab: remote controlled frontend framework for Phoenix

I used ASDF to upgrade elixir and erlang,
I will try… brew upgrade erlang

…==> Upgrading 1 outdated package, with result:
erlang 20.1.5 -> 21.0.5
==> Upgrading erlang
==> Installing dependencies for erlang: automake, openssl, jpeg, libpng, libtiff, wxmac

continuing…
==> Installing erlang dependency: openssl
==> Downloading https://www.openssl.org/source/openssl-1.0.2o.tar.gz

I think I have found the problem - my 2009 macbook pro is so old that my version of OSX (10.10) is no longer supported.
I have been putting off upgrading because the macbook pros have been so awful over the last 3 years.
Looks like I will have to finally upgrade to the 2018 macbook pro.

during the erlang upgrade I got…

Error: You are using macOS 10.10.
We (and Apple) do not provide support for this old version.
You will encounter build failures and other breakages.
Please create pull-requests instead of asking for help on Homebrew’s
GitHub, Discourse, Twitter or IRC. As you are running this old version,
you are responsible for resolving any issues you experience.

will be back when I have upgraded

Yeah if only we could grab that and the origin via the socket constructor… >.>

You could always make your own socket wrapper class, hmm…

Looks awesome overall though!!!

This has nothing to do with Drab and is purely a broken OTP installation, google can assist with that (as I don’t use a mac ^.^;). :slight_smile:

It is not a problem if I want to read the session cookie, I can always clone it in the plug. The issue is I can’t write it anyhow.
Anyway, looks like session will remain read-only in Drab. But it could be good to have some store, session or whatever to exchange informations between controller and commander world.

Elaborate, please!

Well you know the transports used, like on the line:

transport :websocket, Phoenix.Transports.WebSocket

The Phoenix.Transports.WebSocket part is just fulfilling a behaviour, it might be possible to make a module that just delegates back to that one but overrides a few key functions on it to perform the cookie work again (and don’t forget the origin check!), that would be a great standalone library actually…

1 Like

Ahoy, @OvermindDL1, I’ve just started adding Drab to Unpoly to handle some more interactivity to my search results page that I couldn’t achieve with Unpoly alone.

Things work peachy until Unpoly swaps out my search results page with a different set of search results. Elements that have been decorated with “drab-click” and “drab-change” no longer work! I’ve tried using shared controllers as you suggested, but to no avail…

What code you use to reinitialize Drab onto newly inserted content? Saw that you were the other kid on the block using the Drab + Unpoly combo, hoping you’ve run into this problem before!

4 Likes

You have to actually setup an Unpoly Router to ‘decorate’ the elements with the Drab javascript library again. I’ve been meaning to pull mine out into a Drab addon (DrabUnpoly?) but it’s so tied in with the rest of my stuff and I’ve been so out of time that I’ve not been able to yet. ^.^

@grych Can help with the proper calls to reinitialize elements with Drab, just add those to an Unpoly router on load/unload and so forth. :slight_smile:

3 Likes

There is a function Drab.enable_drab_on(element) which reinitializes Drabbed elements. See Drab.Client – drab v0.10.5

2 Likes

up.compiler("#up-results", function() { Drab.enable_drab_on("#up-results") }); does the trick! Thx for the heads up and thx for open-sourcing Drab.

2 Likes

That was it! Thanks much! :slight_smile:

There’s actually a compiler/router callback for handling ‘all elements that have been loaded via some kind of ajax call’ that is a convenient place to put such things in. :slight_smile:

1 Like

Hey, I have been trying Drab out.
I have a question about Broadcasting.

I have a genserver doing some stuff, and I would like Drab to update the assigns on a page when an event happens in the GenServer. For instance, when data is added to the genserver I want to push this to the live view using Drab.
I read that it you dont pass the socket to the broadcast function then you need to pass in all assigns and view etc. But I wont really have the data for all assigns, so this doesnt seem like a possibility?

So my question is, what would be the best way to do this?
Can I store the socket in a Drab store and retrive it in the genserver?
Is there some way to get the socket info into other parts of my application without user interaction on the frontend?

Lastly, if I wanted to trigger a drab call from a JS function, like using a timeout to “ping” the Drab event handlers, how would I do that?

Sorry for all the questions, just trying to understand how to use Drab for server side events, not when a users has to interact with the page.

thanks alot

Yes, you can give a topic to any broadcasting function instead of the socket.
And yes, to broadcast poke (update of an assign) you need to give all assigns values. The reason is very simple: every browser may have different state at the same time. Broadcasting poke overwrites all the states to the given one.
This is why I should not do a broadcasting version of poke. I recommend you to use traditional DOM updates (from Drab.Element for example) when broadcasting.
See Allow to broadcast using poke / peek · Issue #60 · grych/drab · GitHub for more.

It is not practical. The socket is just a struct, where the most important information is a PID of the channel process (and in case you are using Drab, the PID of the Drab process).
When browser is disconnected, channel and drab process dies.
There is an idea to replace socket with something more abstract, which identifies a browser. In this case you might send an update to the browser which is disconnected. See 54) ElixirConf US 2018 – Closing Keynote – Chris McCord - #76 by grych

Easy, just use Drab.exec_elixir() method from JS. See docs for Drab.Client.

2 Likes

Almost ready to deploy my app on Gigalixir, running into a strange “invalid quoted expression” error when I try pushing to Gigalixir… has anyone run into something like this before? Everything works fine in development mode and compiles when I run mix release --env=prod, but things fall apart once I run git push gigalixir master

My Gigalixir setup is based on ElixirCasts great tutorial: https://elixircasts.io/deploying-with-gigalixir

My versions of drab and distillery are… {:distillery, "~> 1.0.0"}, {:drab, "~> 0.9.1"}

The weird compile error starts here…

** (CompileError) lib/ficdb_web/templates/fanfic/index.html.drab: invalid quoted expression: %Drab.Live.Safe{partial: %Drab.Live.Partial{amperes: %{}, assigns: %{}, hash: "giytgmrzha2dsmjz", path: "lib/ficdb_web/templates/fanfic/index.html.drab"}, safe: [{:__block__, [], [{:=, [], [{:tmp1, [], Drab.Live.EExEngine}, [{:__block__, [], [{:=, [], [{:tmp1, [], Drab.Live.EExEngine}, [{:__block__, [], [{:=, [], [{:tmp1, [], Drab.Live.EExEngine}, [["{{{{@drab-partial:giytgmrzha2dsmjz}}}}"], "\n                   <li id=\""]]}, [{:tmp1, [], Drab.Live.EExEngine}, {:case, [generated: true], [{:id, [line: 22], nil}, [do: [{:->, [generated: true], [[safe: {:data, [generated: true], Drab.Live.EExEngine}], {:data, [generated: true], Drab.Live.EExEngine}]}, {:->, [generated: true], [[{:when, [generated: true], [{:bin, [generated: true], Drab.Live.EExEngine}, {:is_binary, [generated: true, context: Drab.Live.EExEngine, import: Kernel], [{:bin, [generated: true], Drab.Live.EExEngine}]}]}], {{:., [generated: true], [{:__aliases__, [generated: true, alias: false], [:Plug, :HTML]}, :html_escape]}, [generated: true], [{:bin, [generated: true], Drab.Live.EExEngine}]}]}, {:->, [generated: true], [[{:other, [generated: true], Drab.Live.EExEngine}], {{:., [line: 22], [{:__aliases__, [line: 22, alias: false], [:Phoenix, :HTML, :Safe]}, :to_iodata]}, [line: 22], [{:other, [line: 22], Drab.Live.EExEngine}]}]}]]]}]]}, "\">\n                     <a class=\"\">\n                     <span drab-ampere=\"ge3tcnrvgqzdonjr\" uk-icon=\""]]}, [{:tmp1, [], Drab.Live.EExEngine}, "{{{{@drab-expr-hash:gm4dqmjzgy3taojt}}}}", {:case, [generated: true], [{{:., [line: 24], [Access, :get]}, [line: 24], [{{:., [line: 24], [{:__aliases__, [line: 24, alias: false], [:Phoenix, :HTML, :Engine]}, :fetch_assign]}, [line: 24], [{:var!, [line: 24, context: Drab.Live.EExEngine, import: Kernel], [{:assigns, [line: 24], Drab.Live.EExEngine}]}, :sort_by_icons]}, {:id, [line: 24], nil}]}, [do: [{:->, [generated: true], [[safe: {:data, [generated: true], Drab.Live.EExEngine}], {:data, [generated: true], Drab.Live.EExEngine}]}, {:->, [generated: true], [[{:when, [generated: true], [{:bin, [generated: true], Drab.Live.EExEngine}, {:is_binary, [generated: true, context: Drab.Live.EExEngine, import: Kernel], [{:bin, [generated: true], Drab.Live.EExEngine}]}]}], {{:., [generated: true], [{:__aliases__, [generated: true, alias: false], [:Plug, :HTML]}, :html_escape]}, [generated: true], [{:bin, [generated: true], Drab.Live.EExEngine}]}]}, {:->, [generated: true], [[{:other, [generated: true], Drab.Live.EExEngine}], {{:., [line: 24], [{:__aliases__, [line: 24, alias: false], [:Phoenix, :HTML, :Safe]}, :to_iodata]}, [line: 24], [{:other, [line: 24], Drab.Live.EExEngine}]}]}]]]}, "{{{{/@drab-expr-hash:gm4dqmjzgy3taojt}}}}"]]}, "\"></span>"]]}, [{:tmp1, [], Drab.Live.EExEngine}, {:case, [generated: true], [{:name, [line: 24], nil}, [do: [{:->, [generated: true], [[safe: {:data, [generated: true], Drab.Live.EExEngine}], {:data, [generated: true], Drab.Live.EExEngine}]}, {:->, [generated: true], [[{:when, [generated: true], [{:bin, [generated: true], Drab.Live.EExEngine}, {:is_binary, [generated: true, context: Drab.Live.EExEngine, import: Kernel], [{:bin, [generated: true], Drab.Live.EExEngine}]}]}], {{:., [generated: true], [{:__aliases__, [generated: true, alias: false], [:Plug, :HTML]}, :html_escape]}, [generated: true], [{:bin, [generated: true], Drab.Live.EExEngine}]}]}, {:->, [generated: true], [[{:other, [generated: true], Drab.Live.EExEngine}], {{:., [line: 24], [{:__aliases__, [line: 24, alias: false], [:Phoenix, :HTML, :Safe]}, :to_iodata]}, [line: 24], [{:other, [line: 24], Drab.Live.EExEngine}]}]}]]]}]]}, "\n                     </a>\n                   </li>\n                   "]}
           (phoenix) /tmp/build/lib/ficdb_web/views/fanfic_view.ex:1: Phoenix.Template.__before_compile__/1
    remote: Command '[u'docker', u'run', u'--rm', u'-e', u'GIGALIXIR_SHOULD_CLEAN_CACHE=False', u'-v', u'/tmp/tmpQCSpSB/skyblue-bland-jabiru:/tmp/app', u'-v', u'/tmp/gigalixir/cache/skyblue-bland-jabiru/:/tmp/cache', u'-v', u'/tmp/tmpQCSpSB/env:/tmp/env', u'--env=USER=www-data', u'us.gcr.io/gigalixir-152404/herokuish:latest']' returned non-zero exit status 1

I have zero dev ops experience under my belt, not quite sure where to begin the debugging process on this. I saw some similar looking errors on Drab’s Github page so I thought to post here rather than reaching out to Gigalixir support. Assistance to get my first Elixir app out the door would be very much appreciated!

Hmm I’ve never deployed any Drab on gigalixir, I need to try it out. But right, it looks more like a bug in Drab.Live than a case for gigalixir support.
In the meantime, please ensure you have Drab in v0.9.3, as there were some similar issues fixed in 0.9.2 as far as I remember. Also, is the Elixir and Erlang version the same on your machine as on gigalixir? I understand you compile the stuff on your machine and send compiled beans there. I am just guessing here…

1 Like

Break it up into shared commanders. I like using lots of shared commanders. :slight_smile:

Actually what I do is just register my commanders to a global pubsub and broadcast to them to let them merge however is appropriate for the given commander (it’s only a single function).

1 Like

Guessing helped me!

It was a rabbit hole of upgrades to get it working, but once I bumped Phoenix, Erlang, Distillery, Elixir, and Drab to the latest and greatest versions, things function again. Release is imminent - thx!

2 Likes

Hey Again.
I am trying to create a channel via Drab. I think I am doing it right?

On the frontend I call
ch = Drab.socket.channel(“user:test”);
ch.join();
but Chrome says “Uncaught ReferenceError: Drab is not defined”

I have Drab Client run setup after my app.js as per docs.
<%= Drab.Client.run(@conn) %>

Do I need to do something else to get the user to join a channel?

Hi @doogs,
I believe the issue is that you need to call Drab.socket.channel() after <%= Drab.Client.run(@conn) %> - this function injects the JS into the browser.

2 Likes

But if you need to call Clent.run after the app.js, how would I be able to connect to a channel when inside another page template/controller, as all layouts will load before the app.js which is at the bottom of the site layout.

I tried putting the code after the Client.run and it still didnt work. -
Uncaught TypeError: Cannot read property ‘channel’ of undefined

Even when I call it from browser console it says channel it isnt defined.
TypeError: Cannot read property ‘channel’ of undefined

I will need more info here.
Can you show us the exact code you are doing?
Can you check the page source to see if the Drab’s code has been inserted correctly? Also, please just type “Drab” in the console to see if the object has been initialized correctly. There are cases when Drab JS will not be generated - for example, when commander is missing.

Why do you need your own channel at the first place? I am asking because it might be a XY problem.

1 Like

Thanks, appreciate any help as I am stuck on this one.

So basically, I have a system where Staff members sign in to see their Companies dashboard.
So Each company has data, and all Staff of that Company see the same data in their dashboard.
This data can change at any time.
Ideally I would like to be able to broadcast this assigns data to the Company channel to update all the Staff.
That way I dont have to run exec_elixir from every browser, every second looking for updates. That way I wouldn’t have so many queries running.

When i do “Drab” in console I see that the object is initialised.
But whenever I cal Drab.socket.channel I get the undefined issue.
I have a Commander setup for the controller, and when I use the exec_elixir this updates the assigns correctly.

I used the Drab installer, and checked all those settings from the manual setup, and all looks to be okay.
I am not sure what else to do. Is there any other Socket or Channel Config?
What code would you like to see?