Looks great, any idea when this version will be released?
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
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.
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 !
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.
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 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.
I saw! Whooo!
Looks awesome!
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.
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?
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.
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!
Could always tag a shared commander with @public
or something to have it âalways be in scope in a controllerâ or so?
Heh, well if you are curious, this is slowly replacing the old usages of the erlang web framework Nitrogen.
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).
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).
what should i do if i want to show a confirmation alert before drab-click runs?
for example>
-
Reset
<- Click! - Are you sure? [cancel/ok <- Click!]
- run drab function
There are two easy ways:
- Using
Drab.Waiter
- this is a module to allow you to wait synchronously for the user input. See example: Drab: Server Side User Interface Access - 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.
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?
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
Cool! It worked perfectly!
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!
Short answer is: Yes. And no
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
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
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.
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]
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
Iâll be on the Warsaw conference next week, if someone want to meet and have a chat.