Drab: remote controlled frontend framework for Phoenix

@OvermindDL1,
If you have a server around, and few minutes, could you please run the copy of the Demo page (https://github.com/grych/drab-poc) and expose it to the public? It does not require any additional resources. It would be good if I observe the latency myself, I would know what we are talking about.

I can spool it up for a few moments, getting this error trying to launch it though:

{"Kernel pid terminated",application_controller,"{application_start_failure,drab_poc,{{shutdown,{failed_to_start_child,'Elixir.Sentix',missing_binary}},{'Elixir.DrabPoc',start,[normal,[]]}}}"}
Kernel pid terminated (application_controller) ({application_start_failure,drab_poc,{{shutdown,{failed_to_start_child,'Elixir.Sentix',missing_binary}},{'Elixir.DrabPoc',start,[normal,[]]}}})

Iā€™m googling around for whatever Sentix is nowā€¦

And it needed something called fswatch, which is not in the repositories for linux here apparently, so built it manually. Now it is crashing on startup with:

{"Kernel pid terminated",application_controller,"{application_terminated,drab_poc,shutdown}"}
Kernel pid terminated (application_controller) ({application_terminated,drab_poc,shutdown})

Crash dump is being written to: erl_crash.dump...done

Iā€™m not immediately sure where to proceed from hereā€¦ >.>

I added SASL and got a puke of your Supervisor is crashing due to too many restarts of children Sentix, this Sentix thing seems kind ofā€¦ borkedā€¦:

=SUPERVISOR REPORT==== 19-May-2017::14:31:24 ===
     Supervisor: {local,'Elixir.DrabPoc.Supervisor'}
     Context:    shutdown
     Reason:     reached_max_restart_intensity
     Offender:   [{pid,<0.385.0>},
                  {id,'Elixir.Sentix'},
                  {mfargs,
                      {'Elixir.Sentix',start_link,
                          [access_log,
                           [<<"/home/overminddl1/blah.log">>],
                           [{monitor,inotify_monitor},
                            {latency,1},
                            {filter,[updated]}]]}},
                  {restart_type,permanent},
                  {shutdown,5000},
                  {child_type,worker}]

Sentix has a wonderful lack of logs, so no clue what it is complaining aboutā€¦

Oh I am sorry, I forgot about Sentix - it needs fswatch. Also, the application exposes your access.log, so I should not ask you to run it! Please forget about it, and thanks a lot for trying!

I will build some basic button/response example application and ask you again, if you donā€™t mind :slight_smile:

I already noticed that and pointed it elsewhere. :wink:

1 Like

And yeah, Sentix is crashing somehow and it is not detailing ā€˜whyā€™, great error reporting it has thereā€¦

For note, you are already including :fs, which has file watching as I recall (it comes with phoenix), Iā€™m unsure if Sentix is even needed?

Stripped it out a bit, got it running, forgot to run npm so it is unstyled, running npmā€™s stuff now. ^.^

And done, Iā€™ll leave it up for a few short period @grych so be quick. ^.^

EDIT1: The demo page is SO much faster responding to me here, like wow a difference!
I think maybe a set of commands should be batched and marshaled to the javascript side, could do simple conditionals and all sorts of things.

EDIT2: Testing complete, he appeared, we chatted, he tested. :slight_smile:

1 Like

Request for Comment

Ladies and Gentlemen,
this post is to discuss the new idea of treating the living pages in Drab, completely apart of jQuery or other JS libraries. The new approach should be more phoenix- and elixir-like!

TL;DR: demo page

Drab.Live

The goal is to re-use the Phoenix Templates with the living pages. When you render a template, you give some assigns to the template code. Drab.Live introduces the way to update the assigns on the fly!

Imagine you have got your template, rendered somewhere in the Controller:

<%= for user <- @users do %>
  Username: <%= user %> <br>
<% end %>

And now imagine you can change the value of the assign on the fly. When you do

Drab.Live.poke socket, users: ["some", "other", "list"]

it immediately changes the corresponding part of the page in the browser.

There is also a function Drab.Live.peek/2 to read an existing value the assign.

How Does it Work

Having the own EEx Engine, it surrounds the code with <span>, having it easy to find & replace the value from the JavaScript. Warning: it does not work inside the tag yet:

  • works: <a href="a"><%= @link_name %></a>
  • does not work: <a href="<%= @href %>">Click Me</a>

I am planning to sort it out next.

Demo

Please check the demo page for live examples.

TODO

There is a huge list of TODO, before it became usable:

  • allow to live update inside a tag, eg <div class="<%=@class%>">
  • internal changes, like storing code partials inside a View instead of storing them on the page
  • crypt everything which is stored on the client-side
  • work on partials, propagate poke changes only into selected partials
  • have a values of the <input> fields directly in the Commander Event Handler Function
5 Likes

This looks pretty awesome. Changing the assigns is a smart way to propagate changes to everything that needs itā€¦ and to select on the partials that care about those assigns makes it feel a lot like components. :smile:

Iā€™m wondering if an additional listener on the url bar would make sense? Or a convenience method to update the url bar?

Just thinking if a user is on /playlist/234 then navigates to /playlist/99ā€¦ youā€™re really only updating the assigns in the way youā€™ve just built. But a user may want to share the url or reload the page and so ideally the address bar should update.

I know you mentioned earlier routing is out of scope which is totally understandable. But if updating the address bar could be something small and similar to updating like a div or something like youā€™re already doing, I think it could be useful.

1 Like

This is awesome! And is precisely how I use unpoly.js as well. :slight_smile:

This could be quite useful indeed. :slight_smile:

For note, if you want a reference of how things work, when someone clicks on, say, a button on one of my unpoly decorated pages, that element also has a decoration on it to say what it effects (which can be multiple, specific updates, etcā€¦) and that information is sent to the server as well. My phoenix templates by default re-render everything and send the entire page to unpoly, then it picks and chooses out of that what to put in. However, using the information of ā€˜whatā€™ it wants to update I can re-render only parts of the page, calculating only part of the data I need (perhaps excepting some very costly database calls for example) then just send the parts back that it wants (or more, or even less, whichever) and it stitches it together on the front-end. If no javascript on the browser they are using then full pages are reloaded as normal. :slight_smile:

2 Likes

@gdub01,
Iā€™ve got your point. You are right, it should be a way to update the URL in such cases. The best could be if it would be updated automatically, when you poke the corresponding assign. I canā€™t see the way how to do it right now, but obviously the helper function to update the URL is possible. I will add it to TODO list.

@OvermindDL1,
well, your posts inspired me to go that way :slight_smile: Additionally, Iā€™d like to create something which is intuitive to Phoenix programmer. Less API, more Magic :wink:

3 Likes

I installed the library, looks interesting, I want to use it but am tied to Materialize rather than Bootstrap, will it work without bootstrap?

Yes. You wonā€™t be able to use only Drab.Modal, which depends on bootstrap+jquery

Thank you! Donā€™t suppose you know how to fix the issue with

$(document).ready(function() { $('.chips-autocomplete').material_chip({ getting a Uncaught ReferenceError: $ is not defined, error (this only started after I modified my config to use the npm setting in the brunch config)

I am not very familiar with Material, but it looks like you donā€™t have jQuery defined as a global. You can set it up in the brunch config like:

"dependencies": {
  "jquery": ">= 3.1.1",...

npm: {globals: {
  $: 'jquery',...

I do (did) have npm set up as you directed in the git readme, but wasnā€™t able to get it to work, will fork my code and transition over to bootstrap, see how i like it :slight_smile:

Thanks for the help

Hi all,
Drab.Live update: attributes goes live!

<a href="<%= @href %>" class="<%= @classes %>"><%= @link %></a>

Now, you may update any of this assigns in the Commander to immediate update it on the page

def button_clicked(socket, _sender) do
  classes = peek(socket, :classes) <> " red-big-button"
  poke socket, href: "https://tg.pl/drab", classes: classes
end

Demo

Iā€™ve made a small demo here. You may type down a Bootstrap class to observe how it changes, live.

How Does it Work

Drab actually updates the attribute. It does not re-render or replace the tag, so if youā€™ve done any updates with JS, it would be kept. But, who needs JS when you have Drab? :slight_smile:

Limitations

It works only with named attributes. So constructions like:

<tag <%= attr %>>

are prohibited. For now, such code does not compile as a Drab Template (but will show a friendly messages during compile time). I still how no idea how I could handle it, so I will leave it as it is. Do you think it is worth to make an effort to make it work?

Story

Again, the goal is to find the best, Phoenix-like API to update the page on the fly. I believe this is a good way to do it, as you need only minimal (or none) changes to your Phoenix templates to go live with Drab.

TODO

  • work on partials, propagate poke changes only into selected partials
  • have a values of the fields directly in the Commander Event Handler Function
  • a handy way to update the url bar
1 Like

Whoo looks awesome! :slight_smile:

Hmm, there are cases, but maybe signifying it via a delimiter of some form? Like Polymer does blah="{{variable}}" to set the property on the element named blah to the contents of variable, and it uses blah$="{{variable}}" to set the *attribute*. A difference is if it is explicitly falsethen it removes the attribute entirely, if it is non-false then it sets the attribute to that value with the special case of if that value istrue(not the string"true"but the booleantruethen it just sets it as a true attribute (think therequired` attribute on input boxes). You have to have some way to differentiate between property and attribute setting as they can do very very different things depending on the DOM node being updated.

1 Like

True. I would like to be compatible with ā€œnormalā€ Phoenix tempate, so I would rather do foo=<%=@bar%> for attribute and foo$=<%=@bar%> for the property.

So far Iā€™ve found the following ways to deal with nodes:

Helper functions and changeset

Something like below, query for the selector, do a changes on the specific node(s) and push the modified node:

node = query(socket, "#selector")
node = set_property(node, "property", false)
node = set_attribute(node, "other", "blah")
push socket, node

Bound property to assign in JS

<script>
  node.some_property = <%= @assign %>
</script>

Each time the @assign is poked from the Commander, script is re-evaluated. This must be done anyway, for other cases.

Template (Polymer styla)

<tag attribute=<%= @attribute %> property$=<%= @property %>>

This would bound the the property with the assign.

Hear hear, as long as can still be used within templates too. :slight_smile:

That is backwards from Polymer and other template libraries where $ is used for attributes and without $ is used for properties (properties are a lot more used than attributes). :slight_smile:

This idea is great and please keep up the momentum!

Looking through this and firing up the demo I was wondering what the security implications of this are?

While magic can be great ,it can be a problem for folks learning/reading the library when there is too much magic (perl, rails)ā€¦ Please make the magic optional and try to document it if possible.

1 Like