Drab: remote controlled frontend framework for Phoenix

web-frameworks
frontend
drab
javascript
phoenix

#322

Looks great, any idea when this version will be released?


#323

I am currently working on it. I want to introduce this in the v0.7.2, hopefully in a week or two. All depends on the weather over the weekends :slight_smile:

In this release system will generate warnings about undeclared handlers, but it will still work. In 0.8, I am going to completely depreciate it.


#324

Thanks for your answer Grych. Thats true that returning sockets would be a nice improvement and generate less code and grouping instructions would be great ! If I may add, I think that replacing the whole element classes is a bit heavy, should be a method to add or remove a specific class or attribute (like jquery)…

For partials i couldn’t find a way to use it (I generate n times the same partial which is a button). If you have a chance to show a demo that would be great… Anyway thanks !


#325

I will think about it. By the other hand, I don’t want to overcomplicate Drab.Element - it is supposed to be the simplest one.

Did you check Drab.Query - the drab library over jQuery? You may find it useful, if you are using jQuery. It has the class manipulation functions.


#326

This is exactly what the next release will be about.

This was kinda hard to accomplish, but now I think I found the way how to do it with an elegance. With Drab, there are three ways to update the content of the page: using the assign name (Drab.Live), with the CSS selector (all other modules) or just send javascript (Drab.Core.exec_js). If you use exec_js, you know exactly what you’re doing, and there is no help from the Drab side :slight_smile: More interesting are those two update types:

Update with CSS Selector

Having the page as below, we want that clicking the button updates .spaceholder1 only within the range of drab-commander.

  <div drab-commander="DrabTestApp.Shared1Commander">
    <div class="spaceholder1">Nothing</div>
    <button drab-click="button_clicked">Shared 1</button>
  </div>
  <div drab-commander="DrabTestApp.Shared1Commander">
    <div class="spaceholder1">Nothing</div>
    <button drab-click="button_clicked">Shared 2</button>
  </div>

Now, you can accomplish it by passing additional arguments, like output tag id, but it is not very flexible. What if we could select just this drab-commander tag, where the button was clicked? Just like we can use Drab.Core.this/1 to select the exact sender of the event, we may have Drab.Core.this_commander/1, to build a selector which choses the desired object:

  defhandler button_clicked(socket, sender) do
    set_prop socket, this_commander(sender) <> " .spaceholder1", innerText: "changed"
  end

Notice the space before “.spaceholder1”. this_commander/1 returns the string like [drab-id="f59d54e6-a924-4e72-90d1-5177efecac9b"], so you may build any selector based on it.

Update with the living assigns

However, where using living assigns, we are not using any CSS selectors - everything updates under the hood. In this case, this functionality will be always turned on. Every update inside the drab-commander will be regional by default.

  <div drab-commander="DrabTestApp.Shared1Commander">
    <div><%= @assign1 %></div>
    <button drab-click="button_clicked">Shared 1</button>
  </div>
  <div drab-commander="DrabTestApp.Shared1Commander">
    <div><%= @assign1 %></div>
    <button drab-click="button_clicked">Shared 2</button>
  </div>

And the handler is just exactly as it was before. Local changes are out of the box.

  defhandler button_clicked(socket, sender) do
    poke socket, assign1: "changed"
  end

I believe it makes sense and finally provides a way to create a kind of reusable components.

One more thing: why not to enable this functionality with the selector updates by default? I wanted to do it like this, but I realized that it would provide way too much magic. When using a selector, you expect some behaviour.


#327

I saw! Whooo!

Looks awesome! :smiley:


#328

v0.7.2.

This release introduces core API changes, and the important new feature: step forward to reusable components!

API changes

use Drab.Controller is now optional

When using default commander name, corresponding to the controller (like PageCommander -> PageController), there is no need to mark controller as Drab.Controller anymore.

Shared Commanders should be declared in the page controller

All shared commanders must be explicitly declared in the controller:

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

In this version, system generates warning message if commanders are not declared. This warning will become error in v0.8.0

defhandler macro for creating event handlers in commanders

Since this version, all event handlers, whenever they are in shared or “normal” commander, must be declared with public or defhandler macro. Use defhandler instead of the standard def.

This:

public :button_clicked
def button_clicked(socket, sender), do: ...

is an equivalent of:

defhandler button_clicked(socket, sender), do: ...

In this version, system generates warning message if the function is not declared as handler. This warning will become error in v0.8.0

New Features

Create Reusable Drab Components with Shared Commanders

Accomplished this with the new Drab.Core.this_commander/1 function, returning the unique selector of the sourrounding commander tag, so you may easly reduce the region where your update works.

Having the page as below, we want the button to update .spaceholder1 only within the range of drab-commander.

<div drab-commander="DrabTestApp.Shared1Commander">
  <div class="spaceholder1">Nothing</div>
  <button drab-click="button_clicked">Shared 1</button>
</div>
<div drab-commander="DrabTestApp.Shared1Commander">
  <div class="spaceholder1">Nothing</div>
  <button drab-click="button_clicked">Shared 2</button>
</div>

Just like we can use Drab.Core.this/1 to select the exact sender of the event, we may have Drab.Core.this_commander/1, to build a selector which chooses the desired object:

defhandler button_clicked(socket, sender) do
  set_prop socket, this_commander(sender) <> " .spaceholder1", innerText: "changed"
end

Notice the space before “.spaceholder1”. this_commander/1 returns the string like [drab-id="f59d54e6-a924-4e72-90d1-5177efecac9b"], so you may build any selector based on it.


#329

What about when a shared commander loads ‘another’ shared command, like doing in-page navigation to other ‘components/pages’? Does that mean you have to list all possible shared commanders on all possible controllers?


#330

Yes.
Drab needs to know all the commanders playing the role in that scene, if we need to unify shared commanders with page ones, to get callbacks working.


#331

But, I’ve got your concerns now. It might be a loooooong list sometimes.
What about allowing any shared commander, but callbacks working in the declared?

BTW. This is why I am constantly spamming you guys on new ideas and features. I am working on Drab by only myself, so any other opinion is highly appreciated!


#332

Could always tag a shared commander with @public or something to have it ‘always be in scope in a controller’ or so? :slight_smile:

Heh, well if you are curious, this is slowly replacing the old usages of the erlang web framework Nitrogen. :slight_smile:

In essence with nitrogen everything was ‘components’, which you could swap and replace and do all kinds of things on a page (and it mostly gracefully fell back without javascript too, mostly, depending on how you coded your side), and you built a page with an initial set of components (which could then do all manner of stuff inside their own little areas). They sent html with embedded javascript initially, but acted a lot like drab overall (though in Erlang). :slight_smile:

Maybe take a look at nitrogen, see if you get some ideas of ‘how’ to use it (not necessarily it’s implementation or API though). :slight_smile:


#333

what should i do if i want to show a confirmation alert before drab-click runs?

for example>

  1. Reset <- Click!
  2. Are you sure? [cancel/ok <- Click!]
  3. run drab function

#334

There are two easy ways:

  1. Using Drab.Waiter - this is a module to allow you to wait synchronously for the user input. See example: https://tg.pl/drab#waiter
  2. Using Drab.Modal.alert/4, which shows you a nice bootstrap modal. But this requires jQuery (as bootstrap does). Here is a good example - it shows modal to ask if the processing should go on.

#335

thanks, I like the way Drab.Waiter treats user input. it is so simple.

but on it’s documentation, i couldn’t find a clue how to use it with window.confirm()

waiter(socket) do
  on "selector1", "event_name", fn (sender) ->
  end
  ...
end

what am i missing?


#336

Well, if you want to use confirm() there is no need to use waiter! confirm() is already waiting for the user input:

{:ok, confirmed?) = exec_js socket, "window.confirm('Sure?')"

Just remember about the timeout. The default is 5 seconds, and after this you will get {:timeout, "timed out after 5000 ms."}.

{:ok, confirmed?) = exec_js socket, "window.confirm('Sure?')", timeout: :infinity

#337

Cool! It worked perfectly!


#338

So a quick question, I guess. Drab looks great. But how, exactly, do you use Drab in the sense of integrating with the rest of your app?

So say I have a a task on my dashboard, and I use a dropdown menu to assign it to an employee. That fires off a Drab handler, and then what? In my Commander do I import all my Contexts and call the update functions on that, or does all of that live in my Controller, or what?

Also, when you render a partial, does it call the corresponding Phoenix Controller for that template and all the plugs, etc that live in that Controller?

I like the idea of a bunch of shared controllers so I can have my “master” controller that mostly just lays out the page and calls a bunch of rendered partial templates, and Drab can update those things. But how does this work in practice with actually updating my database and responding to updates made elsewhere in the app?

I want to, for example, have a “edit” or “new” form for part of my page, and post the data to the server without reloading the entire page, yet other parts of the page get updated from that post, or other viewers of the page. What’s the basic workflow for that kind of thing? Are the Commanders essentially replacements for the Controller at that point?

Thanks, Drab looks awesome!


#339

Short answer is: Yes. And no :slight_smile:

Controller is a module containing functions, which are triggered by the outside world - http requests, like GET or POST.

Commander is a module containing functions, which are triggered by the outside world - JS events, like “onclick” or “onsubmit”. So I would rather say that the commander is a replacement for a JavaScript controller of some framework. The only difference is that the event handlers run on the server-side :slight_smile:

Controller uses routings to find out which function it should run, based on the http request ("/post/1" launches MyApp.MyController.show etc).

Commander does not use any routings. It knows which commander to use, because it knows which controller was used to render the page. So this event on <button drab-click='show_user'>
would run MyApp.MyCommander.show_user. Or you can use drab-click='MyApp.,OtherCommander.show_user' to route it directly.

At the beginning of the project I was thinking about putting the event handlers in the controllers, but I think creating another entity just for the event handlers was a good idea. You still have a MVC application, but “C” is for commander :slight_smile:

Yes.
If you want to update the part of the page with some partial, you use Phoenix.View.render_to_string/3 to get a html, and push it to the page.

poke socket, live_partial1: render_to_string("partial1.html", color: "#aaaabb")

The above uses render_to_string/2 helper, more info here.

It all depends on you. If you want to create 100% drabbed application, you may only render the page with the controller, and then do all the edits with the commander.


#340

Now it is possible, you may pass options to Phoenix.Channel. To turn off the logs:

config :drab, :phoenix_channel_options, [log_handle_in: false]

#341

Girls and boys, Drab v0.7.5 is out!

This version finally contains all the features of Shared Commanders I wanted to introduce, especially magic updates for living assigns in the specific shared commander - see the new demo example.

Now I am planning to focus on the living assigns again, as I have an idea how to completely remove all the limitations of the engine. Then, two big features must be introduced before we reach 1.0: event handler test framework and optimistic updates. Also, the project needs a code and documentation review, as the core evolved from the proof of concept page and never been changed :slight_smile:

I’ll be on the Warsaw conference next week, if someone want to meet and have a chat.