Drab: remote controlled frontend framework for Phoenix

Maybe it would be good to report it to plug team? They may want to check if you have a cipher before trying to use it, and change it, or at least give a better error message.

Good idea. I submitted it. Thanks again.

Hello,
I adopted Drab recently, and iā€™m facing a problem (surely some kind of noob problem though):
How can I test commanders?

I used to test all my controllers, and have some controller-related code untested made me feel uncomfortable.
I suppose it is close to channel test (and I only work with channel when I go through Phoenix bookā€¦) but I donā€™t have any clue what to send to socket.
Or maybe, this should be tested via headless browser like any front end?

Tanks your your help!

There is no support for tests in Drab yet. It must be done in the way to the stable production version, but I still have no idea how could it look like :slight_smile:

In internal drab tests, I am doing a full integration tests with hound and chromedriver. Unfortunately, the headless browser (phantomjs) does not want to work with Drab.

Hello Grych

I get a protocol Enumerable not implemented for nil while using the Drab.run_handler method on Phx 1.3. I try to reproduce your example but get the error bellow. Any suggestion ?

  <a class="waves-effect waves-light btn grey" onclick="Drab.run_handler('click', 'clicked', {click: 'clickety-click'});">

  def clicked(socket, payload) do
     socket |> Drab.Browser.console("You've sent me this: #{payload |> inspect}")
  end

  Transport:  Phoenix.Transports.WebSocket
  Parameters: %{"event" => "click", "event_handler_function" => "clicked", "payload" => %{"click" => "clickety-click"}, "reply_to" => "8a0affa7-1dd2-4e9d-95d8-9441897eb747"}
  [error] Drab Handler failed with the following exception:
  ** (Protocol.UndefinedError) protocol Enumerable not implemented for nil. This protocol is implemented for: ...

I also receive this after closing the alert box :

 [debug] INCOMING "execjs" on "__drab:same_path:/packs/1" to Drab.Channel
 Transport:  Phoenix.Transports.WebSocket
 Parameters: %{"ok" =>["SFMyNTY.g3QAAAACZAAEZGF0YWgCZ2QADW5vbm9kZUBub2hvc3QAAA6mAAAAAAByAANkAA1ub25vZGVAbm9ob3N0AAAAj__RLAABgxyz62QABnNpZ25lZG4GAAPPMjNfAQ.2KVP7lyR4fOT_q4tZNBGdCA3dzZFZX5fXWMV_gc-Xjg", nil]}
1 Like

Itā€™s a bug. Thanks a lot for reporting!
Could you please create an issue on github, so we will have all bugfixes in the one place.

1 Like

the issue is done !
thanks

1 Like

v0.6.0-pre.1 finally arrived

Girls and boys,
after a long time, I am happy with the brand new Drab EEx engine, completely rewritten, shorter, faster and way more clever. It is finally not pushing <span> in every corner of your html template, but it is using the existing tags.

Please help me with testing this one.

  • add {:drab, "~> 0.6.0-pre.1"} to your deps in mix.exs
  • mix deps.get
  • rm priv/hashes_expressions.drab.cache.* (itā€™s been renamed to drab.live.cache)
  • mix clean to recompile templates

Changes

The main change in the new template engine is that now it is not injecting <span> everywhere. Now, it parses the html and tries to find the sourrounding tag and mark it with the attribute called drab-ampere. The attribute value is a hash of the previous buffer and the expression, so it is considered unique.

Consider the template, with initial value of 1 (given in render function in the Controller, as usual):

<p>Chapter <%= @chapter_no %>.</p>

which renders to:

<p drab-ampere="someid">Chapter 1.</p>

This drab-ampere attribute is injected automatically by Drab.Live.EExEngine. Updating the @chapter_no assign in the Drab Commander, by using poke/2:

chapter = peek(socket, :chapter_no)     # get the current value of `@chapter_no`
poke(socket, chapter_no: chapter + 1)   # push the new value to the browser

will change the innerHTML of the <p drab-ampere="someid"> to ā€œChapter 2.ā€ by executing the following JS on the browser:

document.querySelector('[drab-ampere=someid]').innerHTML = "Chapter 2."

This is possible because during the compile phase, Drab stores the drab-ampere and the corresponding pattern in the cache DETS file (located in priv/drab.live.cache).

Sometimes it must add a <span>

In case, when Drab canā€™t find the parent tag, it injects <span> in the generated html. For example, template like:

Chapter <%= @chapter_no %>.

renders to:

Chapter <span drab-ampere="someid">1</span>.

Avoiding using Drab (nodrab option)

If there is no need to use Drab with some expression, you may mark it with nodrab/1 function. Such expressions will be treated as a ā€œnormalā€ Phoenix expressions and will not be updatable by poke/2.

<p>Chapter <%= nodrab(@chapter_no) %>.</p>

In the future (Elixir 1.6 I suppose), the nodrab keyword will be replaced by a special EEx mark / (expression will look like <%/ @chapter_no %>).

The @conn case

The @conn assign is often used in Phoenix templates. Drab considers it read-only, you can not update it with poke/2. And, because it is often quite hudge, may significantly increase the number of data sent to the browser. This is why Drab treats all expressions with only one assign, which happen to be @conn, as a nodrab assign.

6 Likes

I am working on a small experimental app that incorporates geolocation into an interactive chat experience. I want the LiveCommander to not only have lat/lon available in @lat/@lon variables, but also be able to respond to GeoLocation changes and update the page with fresh info that is location specific. I could do some JS code that somehow combined Geolocation.watchPosition()) with sending info to a phoenix channel to update the phoenix session, but am wondering if there is better way to incorporate geo position changes with LiveCommander, some sort of drab-event that could be created, or some other way to do it that fits the drab model better.

Drab does not have any geolocation helpers (maybe it is a good idea to add it in the Drab.Browser?).

To archive your goal, you may call javascript function (from Geowatchin.watchPosition()) Drab.run_handler() to execute function in the commander module directly from the browser.

Iā€™m surprised pheonix doesnā€™t have something like this built-in. It appears to be the perfect fit for the strengths of Elixir and Pheonix while not being the same JavaScript SPA hell that is currently out there. Itā€™s exhausting.

With polish, Drab could be a viable way to build rich applications on the server side that appear to be client side in the browser.

I think this approach should be a default option in Pheonix!

Great work btw, @grych

1 Like

Itā€™s not thaaaat easy to implement ā€œsomething like drabā€. Once you start going that way, you start hitting some problems, some of them serious.

With English too xD A great thing about Drab is that internationalization is dead-simple. Compare to doing something like that with VueJSā€¦

It depends on what kind of application youā€™re talking about. The latency of going client-to-server is really high with Drab. This is obvious to most people who try Drabā€™s demo webpage from outside Europe. Such applications wonā€™t appear client-side to anyone who pays attention to latency. Drab is good, but itā€™s not a replacement for client-side JS.

That said, even though it might not be the best experience for the end-user, the ability to use Elixir for everything is great.

2 Likes

OK, perhaps my comment was too simple. Phoenix has a unique set of features that make stuff like this possible and potentially performant for broad use cases.

Having a client -> server -> client communication path can be slow but it isnā€™t any slower than traditional server rendered web apps. Itā€™s even faster using web sockets. Is Drab doing something different? Perhaps I misunderstood how it works. Anything with a sub 500ms response time is certainly borderline reasonable for UI with button click actions.

Mostly, I long for the day we donā€™t have to play the JavaScript framework game to push a round peg into the square whole that is the HTML web.

Oh, the English language! It lacks a bit of polish, but maybe all languages do, even Polish.

2 Likes

Phoenix is a great foundation for the projects like Drab.
But building Drab into Phoenix could make it overcomplicated. It is better to keep it as an external library, maybe with the better, automatized installer.

1 Like

Thanks for the pointer.

from inspecting the code it looks like drab run_handler() looks straightforward, with three required parameters, with the handler_function having arity 2. But I am getting an error (see simple example below). Probably something simple so thought I would ask.

from mix.exs
{:drab, ā€œ~> 0.5.6ā€},

commander handler example:
def test_handler(_first, _second) do
IO.puts(ā€œhello form test_handlerā€)
end

from browser console:
Drab.run_handler(ā€œtest_event_nameā€, ā€œtest_handlerā€, {ā€œanswerā€: 42})

The error from server:
ERROR:
[error] Drab Handler failed with the following exception:
** (Protocol.UndefinedError) protocol Enumerable not implemented for nil. This protocol is implemented for: DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Postgrex.Stream, Range, Stream, Timex.Interval
(elixir) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir) lib/enum.ex:116: Enumerable.reduce/3
(elixir) lib/enum.ex:1832: Enum.reduce/3
(drab) lib/drab/core.ex:173: Drab.Core.normalize_params/1
(drab) lib/drab/core.ex:168: Drab.Core.transform_payload/2
(elixir) lib/enum.ex:1811: Enum."-reduce/3-lists^foldl/2-0-"/3
(drab) lib/drab.ex:243: anonymous fn/6 in Drab.handle_event/6

Any advice appreciated. thanks.

Hey @piex,
it is a known bug (https://github.com/grych/drab/issues/62).
It is solved now, you may try master from github, or wait for v0.6.0 (I hope this week!).

BTW in 0.6.0 run_handler becomes exec_elixir :slight_smile:

2 Likes

Drab v0.6.0 released

Please read release notes.

In this release Drab.Live is completely redesigned. It is not pushing <span> in every corner of your html, but it tries to find the parent tag and link to it.

Also, Shared Commanders are introduced. This allows you to create component-like modules.

7 Likes

These are awesome. ^.^

2 Likes

Sorry, this might be off topic. I remember reading somewhere that someone used Drab to build a ā€œload moreā€ UI or an infinite scroll, but I canā€™t remember where the thread was. Does anyone know of something similar or have a link to such a thread/blog post?

1 Like

Not off hand but Iā€™d think it would be pretty trivial to write, itā€™s just an onscroll handler when a condition is met.

2 Likes