Drab: remote controlled frontend framework for Phoenix

web-frameworks
frontend
drab
javascript
phoenix

#241

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.


#242

Good idea. I submitted it. Thanks again.


#243

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!


#244

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.


#245

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]}

#246

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.


#247

the issue is done !
thanks


#248

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.


#249

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.


#250

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.


#251

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


#252

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.


#253

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.


#254

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.


#255

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.


#256

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:


#257

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.


#258

These are awesome. ^.^


#259

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?


#260

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.