Drab: remote controlled frontend framework for Phoenix

Whoo!

Very nice! Reusable components are so convenient. :slight_smile:

Iā€™m curious how this will be done, just like embedded javascript or something?

1 Like

Yes, for the first version I will just simply allow to put JS as a string. Iā€™d like to use ElixirScript, but as you know it is not possible yet.

1 Like

@grych
Hey Tomek!
Great to meet you at the ElixirConf in Warsaw! Keep up the good work!
Best, Frank

2 Likes

Thanks, you too, see you around, at some next event!

2 Likes

Ladies and Gentlemen,
Iā€™ve just released v0.7.7. It is a very important release, may call it experimental, because Iā€™ve completely changed the way how Drab.Live renders the poke updates.

All Drab.Live limits should not exist anymore.

I strongly encourage everyone to give it a try and give me a feedback in case of failure.

3 Likes

Drab v0.8.0 has landed!

codename: rm priv/drab.live.cache

The artifact of the ancient era, DETS cache for metadata from compiled Drab.Live templates, finally reached the end. Since this release, Drab stores every template metadata in the separate module. The compilation time is a fraction of the previous now. Everyone is happy.

This is a significant change, so please upgrade and report any issues

4 Likes

Hi:

I donā€™t know if this is the right place to ask a question but here I go.

Iā€™m trying Drab and using it with Bootstrap eonasdan datetimepicker. That component fires a dp.change event instead of a pure change event when date is changed. I added on a .drab template

 <%= text_input @f, :start_date, drab: "dp.change:start_date_changed", class: "form-control datepicker", "data-date-format": "YYYY-MM-DD" %>

And ofc added the appropriate function in the commander but the event is not being picked up. If I manually set an on(dp.change) binding in the console it picks the event ok.

Is there a limitation on custom events on Drab? Iā€™ve been reading the js code and it seems that it should not be the case.

Any help would be appreciated!

Cheers

Lorenzo

1 Like

Hi @Batou,
congratulations, youā€™ve found a bug!
This is probably because now Drab defines client side events in a very, hmm, naive way. Actually it is:

function update_event_handler(node, event, func) {
  // TODO: learn more about event listeners
  // node.removeEventListener(event, func)
  // node.addEventListener(event, func)
  node["on" + event] = func;
}

As you can see in TODO, I am aware of how poor is this ā€œsolutionā€, it supposed to be a temporary for a proof of conceptā€¦ But it was always something more important to fix/improve.

Maybe someone with the better knowledge of JS may help? One of the issues is that this function may be called multiple times during the life of the page (as someone update the part of the page from the server, for example), and we donā€™t want the elixir event handlers to be called multiple times.

It is a right place. And, if you be so kind and create an issue on a github, so it wonā€™t be fogotten.

2 Likes

The proper way would be to use addEventListener/removeEventListener with a unique key for the function event, then using the key you can store a name in the data section on the element for later lookup (or stored somewhere else or whatever) so you can cancel it without getting many duplicates.

Things like someElement["on" + event] = blah; should never ever ever be done.

3 Likes

Agreed. Unfortunately, in the complex project like Drab, I had to simplify some stuff to move forward. Otherwise I would never publish even the proof of concept :wink: In my defense, at least I realized that it is poor and TODO myself to learn more about itā€¦

6 Likes

Hi all,
this is a request for comment on the proposed, stable API for Drab. There will be an API changes, sorry for that, but this is a last moment, when we reach 1.0 all will be fixed forever.

I think the most painful change will be that one to Drab.Live.peek. It will not return a value, but standard {:ok, value} tuple. Of course there will be a corresponding peek!.

5 Likes

v0.8.1 released

A story about rebuilding Live Engine continues, thanks to all the users who find the bugs and share with me! Especially one guy :slight_smile:

But there is a one more thingā€¦

mix drab.install

Since the beginning, one of Drabā€™s goals is to encourage beginners to start working with modern web frameworks. I know guys, working in IT, but not professional programmers, who are using only plain php, because frameworks are too complicated. Even those, who made this one more step and moved to CherryPy or even Django, never use async model with partial page updates with Ajax. Ajax is too complicated. It may sound strange for us, who made thousands of Ajax based apps, but believe me, it is true. And here comes Drab, mimicking the standard submit -> action model with event -> handler.

But there is also an another barrier: complexity of installing stuff***. Someone read about Phoenix and Drab, want to try and start learning, but he can see the INSTALLATION.md with bullets, use Drab.Socket mambo-jumbo. Many people give up at this point. Now the entry level is lowered to few simple steps:

mix phx.new myapp
cd myapp
vi mix.exs
mix deps.get
mix drab.install

In the future, may it be good to add --with-drab option to mix phx.new?

***) This is also I am still using Floki, even if there are better alternatives (@f34nkā€™s one, for example). You need to have compilers, rusts, etc, etc - too complex for a beginner. But I am planning to enable it as an option for power users, so you always could choose yourself.

5 Likes

Hey everyone,
just had my first try today with drab because I think it offers amazing functionality. Many thanks for all that hard work put in!

So, Iā€™m working with contexts in my elixir app and for taking my first steps, I just took a random eex template, in ā€œweb/templates/user_assets/ā€, renamed it with .drab and generated a drab commander with

mix drab.gen.commander UserAssets/Structure

Drab then did

* creating lib/app_web/commanders/user_assets/structure_commander.ex

like I assumed, but inside structure_commander.ex the module was created as:

defmodule AppWeb.StructureCommander do

missing the context name in between. Is this desired for any reason?

ā€“ edit
Created an Issue at Issue142

2 Likes

Hi @Sorebrez,
this is a bug in the generator, actually I did not think about nested modules while creating this taskā€¦
Could you please open an issue in github, so it will not be forgotten?

2 Likes

v0.8.2 is out

In preparations to the final API, there are new functions poke! and peek!, throwing exceptions in case of disconnection. I did not change the API yet, so old poke and peek behaves as previous, but they are raising depreciation warnings.

There is a plan to introduce the final API in 0.9.0, which will probably be the next release in a couple of weeks.

Take a look at the release notes, as many more changes and fixes are covered in this release.

4 Likes

Drab news

Last few months Iā€™ve been busy with refactoring, bug fixing, depreciating, etc, etc. Not very exciting stuff :wink: and most of the announcement Iā€™ve posted here was about Drab.Live engine refactored, again or getting closer to stable version. Let me introduce next releases, which will contain a little bit more interesting features.

Upcomping v0.8.3 (in internal tests now, coming next week)

This release will go with two features, making life easier, especially for the newcomers to Phoenix.

Runtime subscribe to external topics

Finally, you are not limited to the compile-time topic youā€™ve set with broadcasting/1 macro in the commander. Now you can subscribe/2 to the external topic, receiving broadcasts sent to it.

      iex> subscribe(socket, same_action(MyApp.MyController, :index))
      :ok
      iex> subscribe(socket, same_topic("product_#{42}"))
      :ok 

Conveniences for Phoenix.Presence

If configured (it is disabled by default), tracks the user presence on the topic. As an example, letā€™s display the number of connected users, live:

      defmodule MyAppWeb.MyCommander
        use Drab.Commander
        import Drab.Presence

        broadcasting "global"
        onconnect :connected
        ondisconnect :disconnected

        def connected(socket) do
          broadcast_html socket, "#number_of_users", count_users(socket)
        end

        def disconnected(_store, _session) do
          topic = same_topic("global")
          broadcast_html topic, "#number_of_users", count_users(topic)
        end
      end

Actually, the similar code is already running on the Drab page. See the number in the upper right! And notice that is is actually showing the number of distinct browsers connected to the page. Yeah, Drab sets the browser UUID and stores it in the local storage. This is for anonymous pages, but you may track the presence also based on the user ID, stored in the session, with simple config :drab, :presence, id: :current_user_id.

Upcomping v0.9.0

This will (hopefully) be the last API-chaning and breaking release before 1.0. Lots of depreciations, core API changes, so it might be painful for existing users, but it is worth. The goal is the holy grail for open source projects, v1.0.

Beyond v1.0

After reaching the grail, I plan to focus on the education material, like docs, example page and the beginners guide to elixir |> phoenix |> drab. The goal is to convince more beginners that web programming of living pages does not have to be scary and painful

Then Iā€™ll rest.

6 Likes

Lol, nope never. :wink:

3 Likes

Girls and boys,
v0.8.3 has landed.

Upgrading from the previous releases: please ensure you have :main_phoenix_app configured in config.ex. Iā€™ve fixed a terrible bug of how Drab was searching for the application it is joining to.

6 Likes

v0.9.0 BREAKING RELEASE

This release introduces the final API. There is no intention to change it, unless very
significant errors are found.

If you are using Drab already, prepare for the changes in the configuration and also in the code.

Drab.Live API changed

As described in #127, API has changed. The most painful change is Drab.Live.peek, as it now
returns {:ok, value} or {:error, why}. Raising Drab.Live.peek is for convinience.

Drab.Live.poke returns tuple now as well, to catch update errors or disconnections.

Redesigned Drab.Config

Since this version, Drab is no longer configured globally. This means that you may use it in the
multiple endpoints environments.
This requires configuration API change. Most of the Drab options are now located under
the endpoint module:

config :drab, MyAppWeb.Endpoint,
  otp_app: :my_app_web,
  ...

The endpoint and application name are mandatory.

However, there are still few global options, like :enable_live_scripts. Please read
Drab.Config documentation
for more information.

Do You Want to Know More?

More API changes

All functions returning {:timeout, description} now return just {:error, :timeout}.

Undeclared handler or shared commander raises

All handlers must now be strictly declared by using Drab.Commander.defhandler or
Drab.Commander.public macro.

defhandler my_handler(socket, payload), do: ..

or

public :my_handler
def my_handler(socket, payload), do: ...

The same is with shared commanders, if you want to use it, declare it with Drab.Controller:

use Drab.Controller, commanders: [My.Shared.Commander]

Hard depreciations of various functions

  • [x] Drab.Client.js/2
  • [x] Drab.run_handler()
  • [x] Drab.Browser.console!/2
  • [x] Drab.Browser.redirect_to!/2
  • [x] Drab.Core.broadcast_js!/2

drab-event and drab-handler combination no longer exists

The existing syntax drab-event and drab-handler is removed. Please use the new syntax of:

<tag drab="event:handler">
<input drab="focus:input_focus blur:input_blur"
<input drab-focus="input_focus" drab-blur="input_blur">

Do You Want to Know More?

Updating from <= v0.8.3

config.exs

Replace:

config :drab, main_phoenix_app: :my_app_web, endpoint: MyAppWeb.Endpoint

with:

config :drab, MyAppWeb.Endpoint, otp_app: :my_app_web

Most of the configuration options now must be placed under the endpoint. Please read
Drab.Config documentation for more info.

4 Likes

A question, is there a way to run (javascript or queued command set) code on the client when the connection dies as a fallback that Iā€™m missing? I want to display the connection is lost and disable some elements when the connection dies (which seems to happen after a few hours (24?) it seems as well as other conditions).

1 Like