Drab: remote controlled frontend framework for Phoenix

Indeed it’s fairly trivial and I’ve implemented it a bunch of times in Rails. I’m just curious how the person did it in Drab. When I tried, my layout ended up breaking due to some wrapper elements, but I see now that’s no longer an issue.

3 Likes

After read this and this I wonder if Drab proposal isn’t the best solution for rich interactive web apps.

2 Likes

I wonder the same… I really try to avoid JS logic for everything I do, and I do not miss it. There is always ways to do everything without the need of JS at all, and sometimes you will even get surprised of how easier, better and faster it becomes!

4 Likes

@kelvinst Very much so, I’m amazed how much you can efficiently do in just CSS without a lick of Javascript, from dialog boxes, lightboxes, accordians, expandable sections, more and more and more and more.

3 Likes

Right!!? I’m really tired of listening the old argument: “but the screen blinks when I click on a link”. Which is not true if you’re using phoenix to render HTML on the server! Actually quite the opposite, if you refresh your JS-rich SPAs, you’ll see that having lots of JS to be executed is not that shiny!

I know the argument presented is a very dumb, and there’s lots of other arguments to use a client side framework. My point is that we really do not need a lot of JS to do what people are doing with a lot of JS…

And common! Most of these SPAs and reactive frameworks rely on something that is really bad and hard to manage: A HUGE AND HAIRY STATE!!!

I really advocate a lot for server-side rendering and I think drab is going in a interesting direction to solve some problems you might get when you need some richer interface.

Thanks @grych!

3 Likes

Think with a clear and almost nubie approach, we can observe that whatsapp works exchanging only text, even accross much limited connections, while some web apps simply doesn’t work.

It is really interesting, given that web is text after all, so if you can remotelly control the ui only sending enough and absolutelly necessary javascript to client, I think that it could be a good solution for several problems that needs much more work using SPA tech, even being a very uncommon approach, but it could be explained if you take in account that erlang is not broadely used. This is a kind of secret weapon!

2 Likes

I ran into a problem while trying to incorporate Drab into a project. I have a bunch of helper functions that I’m using all over the application, so I added them to my app’s foo_web.ex:

  def view do
    quote do
      # ...
      use Phoenix.HTML

      import FooWeb.Router.Helpers
      import FooWeb.ErrorHelpers
      import FooWeb.Gettext

      import FooWeb.InputHelpers
      import FooWeb.HTMLHelpers
      import FooWeb.ApplicationHelpers
    end
  end

It seems that Drab isn’t able to find those functions. While reading the docs I stumbled on the Evaluating Expressions section, which might hold some clues.

If I understood that section correctly, drab doesn’t evaluate any import statements beyond the following:

Import Router.Helpers
Import ErrorHelpers
Import Gettext

I tried changing the config, but it seems that a 3 element tuple is hardcoded into the config function.

Any idea how I can include the rest of my modules? I can get around this by fully qualifying the function names (<%= FooWeb.HTMLHelpers.icon_tag ... %>), but obviously that’s not ideal.

1 Like

Please report it as an issue on github. Looks like Config should be more flexible.

1 Like

I’ve changed the config entry for :live_helper_modules, now it gets a list. Will go live with v0.6.2

3 Likes

Request for Comment: Shall the handler process survive websocket disconnections?

Hi folks,
I’ve got a brand new idea how to make life easier with Drab, and need your opinion on it.

Nowadays, when you run the handler function in the commander, Drab runs it in the process, which is linked to the channel. It means that in case of network disconnection, closing browser, etc this process is going to die. This is a safe approach - Drab is all about manipulating the UI, so when UI is gone, Drab is gone.

By the other hand, I want Drab to be helpful to run long-running processes which communicates back to the client (as in this example). When the disconnection happens, this process is killed, and you need to restart it from the scratch. Not very handy, isn’t it?

So, what if the process would not be linked with the channel and would not be killed when the channel closes? And would be able to re-bind the new socket to the running handler process, so it could continue updating the page after the reconnection?

In this case you just don’t care about disconnections. You can run the 24-hour long loop on the server, and don’t even care if the UI is still there or not.

Of course you can’t read the browser when it is disconnected, but all reading function could return {:disconnected, _} instead of raising an error, so you could do something with this. For the UI updating functions: we could stack up all the javascripts generated during the disconnection and send them to the browser on re-connect.

For sure it would be some performance downsides, as I need to store this “sessions” somewhere on the server side, wipe it out from time to time (so introduce handler timeouts as well). But IMO it is worth doing it. What do you think?

2 Likes

Eh, I’d think it would be best to kill it when the channel closes regardless. If the user wants to do some things out of band then a database or some other dedicated process is better for them to start up.

3 Likes

Maybe you could provide a way to tag certain processes as important so those would continue to run while other less important processes could die?

2 Likes

That makes sense. Some functions may become tasks. And in this case we are not changing the Drab’s core, but rather extending its functionality.

2 Likes

Hello,
I’d like to authenticate my Drab socket using Guardian (so I can get the current user in commanders via Guardian.Phoenix.Socket.get_resource/1, for instance), but both Drab and Guardian work by “overriding” the connect callback, so it doesn’t work. Am I doing it wrong?
I’m surprised that I don’t seem to find anyone else having stumbled upon this problem.
(thanks for the great work on this library anyway, so far it’s been a pleasure to use!)

1 Like

Hi @ltrls,
This is true: Drab steals connect callback and therefore, you can’t use your own.
This must be changed, as not only Guardian is the victim. I am still thinking how to change it. The sooner I do it the better, as it will require all existing application to change their user_socket.ex.

There is also onconnect callback in the commander, and I was thinking to change it so it could modify the socket with Guardian.Phoenix.Socket.authenticate/3 there, and return {:ok, modified_socket}. That would be a little bit more work for you, as you need to do it in each commander, but maybe it would be good to have the authentication per commander?

1 Like

Hi all,

In the current version, the only you need to do in your user_socket.ex is to use Drab.Socket. I made like this, because I wanted to simplify the installation procedure, but it was a bad decision, and this is why Drab is not compatible with the other libraries, like Guardian.

From 0.6.4, you could use the following snipped instead of use Drab.Socket:

channel "__drab:*", Drab.Channel

def connect(payload, socket) do
  Drab.Socket.verify(socket, payload)
end

Drab.Socket.verify is going to return {:ok, socket} or :error. The socket is modified with Drab-specific assigns, so if you want to use it with the other libraries, do not forgot to pass the returned version of it:

{:ok, socket) = Drab.Socket.verify(socket, payload)
Guardian.Phoenix.Socket.authenticate(socket, MyApp.Guardian, payload["token"])

[edited because I realized that I don’t have to change the API, but rather to extend it]

2 Likes

This seems like the best way! If we can still use use for simple setups, or write the connect callback ourselves to compose functions in it, you don’t even break projects relying on use :slight_smile:

1 Like

There will be one more thing to change, if you want to pass the external token connect/2 callback: you will need to pass the token somehow from the client. In this case, instead of adding the following to app.html.exs:

<%= Drab.Client.js(@conn) %>

(which BTW I just renamed to Drab.Client.run), you should generate Drab client’s code first, and connect with additional parameters then:

  <%= Drab.Client.generate(@conn) %>
  <script>
    Drab.connect({auth_token: window.my_token});
  </script>

There is also the third way :slight_smile: pass the token via Drab.Client.js/2 as additional assign, like:

<%= Drab.Client.js(@conn, my_token: token) %>

In this case socket returned by Drab.Socket.verify/2 would contain it as an assign, so you may authenticate like:

def connect(token, socket) do
  {:ok, drabbed_socket} = Drab.Socket.verify(socket, token)
  Guardian.Phoenix.Socket.authenticate(drabbed_socket, MyApp.Guardian, drabbed_socket.assigns["my_token"])
end
1 Like

@ltrls,
if you want to try it before the release, the change is in github master.

1 Like

v0.7.0 is out

Possibility to provide own connect/2 callback for socket authentication, etc

Previously, Drab intercepted the connect/2 callback in your UserSocket. Now, there is a possibility to use your own callback:

defmodule MyApp.UserSocket do
  use Phoenix.Socket

  channel "__drab:*", Drab.Channel
  
  def connect(params, socket) do
    Drab.Socket.verify(socket, params)
  end
end

Do You Want to Know More?

Use of the custom marker “/” in Drab templates

This version allow you to use of <%/ %> marker to avoid using Drab.Live for a given expression. The expression would be treaten as a normal Phoenix one, so will be displayed in rendered html, but Drab will have no access to it.

<div>
  <%/ @this_assigns_will_be_displayed_but_not_drabbed %>
</div>

Do You Want to Know More?

Changed event definition core

The existing syntax drab-event and drab-handler attributes does not allow having multiple events on the one DOM object (#73). This form is now depreciated and replaces with the brand new, better syntax of:

<tag drab="event:handler">

Now may set more event on the single object:

<input drab="focus:input_focus blur:input_blur"

or:

<input drab-focus="input_focus" drab-blur="input_blur">

Do You Want to Know More?

Event shorthands list is now configurable

By default, you can use only few arbitrary-chosen shorthands for the event name / handler name (drab-click="clicked") attribute. Now you may configure the list with :events_shorthands config.
See #73.

Style changes:

  • source code formatted with 1.6.0
  • use @impl true in behaviour callbacks
  • started annotating all functions with @spec (so far only few)
  • small style improvements suggested by Credo

Depreciations:

  • Drab.Client.js/2 becomes Drab.Client.run/2
  • drab-event and drab-handler attributes combination replaced by drab
5 Likes