grych / drab Goto Github PK
View Code? Open in Web Editor NEWRemote controlled frontend framework for Phoenix.
Home Page: https://tg.pl/drab
License: MIT License
Remote controlled frontend framework for Phoenix.
Home Page: https://tg.pl/drab
License: MIT License
it seems there is just no reaction on IOS devices (iphone, ipad)
Any idea why ?
Currently trying to run version 0.5.0 in my host of tests at work and I'm getting the error of:
Object doesn't support property or method 'forEach'
On the generated from Drab javascript code of:
Drab.disable_drab_objects = function(disable) {
document.querySelectorAll("[drab-event]").forEach(function(element) {
element['disabled'] = disable
})
}
Of which obviously the return of document.querySelectorAll
does not have forEach
method in ES5, and as this javascript is not being transpiled down, then it is not working. This needs to use a for loop, like as such:
Drab.disable_drab_objects = function(disable) {
var arr = document.querySelectorAll("[drab-event]");
for(var i=0; i<items.length; i++) {
var element = item[i];
element['disabled'] = disable
}
}
And there are probably other areas that are using functions that do not exist in ES5. Might be good to run the entire drab output through a transpiler like babel and see what it changes to fix that in the outputted javascript.
Right now I'm getting javascript errors in browsers that I still have to support. ^.^;
Phoenix: 1.2, Elixir: 1.4.5, Drab: 0.5.3
I had a problem with checkboxes. In sender.params and sender["forms"] I did get the checkbox value, as if it was checked.
HTML:
<input name="mycheckbox" type="hidden" value="false">
<input name="mycheckbox" type="checkbox" value="true" >
I always got mycheckbox="true"
To fix it:
in drab.events.js, function default_payload(sender, event)
change:
if (input.type == 'radio')
into:
if (input.type == 'radio' || input.type = 'checkbox')
** (RuntimeError) could not find drab.gen.commander.ex.eex in any of the sources
lib/mix/phoenix.ex:24: anonymous fn/5 in Mix.Phoenix.copy_from/5
(elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
lib/mix/phoenix.ex:19: Mix.Phoenix.copy_from/5
lib/mix/tasks/drab.gen.commander.ex:24: Mix.Tasks.Drab.Gen.Commander.run/1
(mix) lib/mix/task.ex:300: Mix.Task.run_task/3
(mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2
When an Elixir release is built with Exrm or Distillery, Mix is not available. So, the following code will likely fail.
defmodule Drab.Config do
# ....
def app_name() do
Mix.Project.config()[:app] || raise """
Can't find the main application name. Please check your mix.exs
"""
end
Hello we are using awesome develop with a new project
This is not a bug, but a feature.
We think it could be quite useful for keyboard events to add the key_code within the dom_sender
sometimes, you don't want to process every key pressed.
For instance, want to load an autocomplete pop-up with values for a web-service, but no with every key pressed, because it takes too much CPU.
We don't see how could it be done now.
Thanx
Hi, i´ve tried drab in the dev instance of our app. It´s a rather big one with many (10) nested views. Rebuild times go from 1-2 s to 20 -30s. I can´t publish that app to show you. But perhaps you have an idea how i can debug this.
I tried adding Drab 0.5.4 to a new Phoenix 1.3.0 project. It works fine adding to a normal project but when I add to an umbrella project I get the following error when running mix phx.server
from the top level of the umbrella project.
$ mix phx.new app --umbrella
#...
cd app_umbrella
# vim apps/app_web/mix.exs to add drab dep
$ mix ecto.create
#...
$ mix phx.server
[info] Application drab exited: exited in: Drab.Supervisor.start(:normal, [])
** (EXIT) an exception was raised:
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:error, :bad_name}. This protocol is implemented for: Atom, BitString, Date, DateTime, Decimal, Ecto.Date, Ecto.DateTime, Ecto.Time, Float, Integer, List, NaiveDateTime, Postgrex.Copy, Postgrex.Query, Postgrex.Stream, Time, URI, Version, Version.Requirement
(elixir) /private/tmp/elixir-20170801-32483-1rf8an1/elixir-1.5.1/lib/elixir/lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir) /private/tmp/elixir-20170801-32483-1rf8an1/elixir-1.5.1/lib/elixir/lib/string/chars.ex:22: String.Chars.to_string/1
(drab) lib/drab/live/cache.ex:60: Drab.Live.Cache.cache_file/0
(drab) lib/drab/live/cache.ex:24: Drab.Live.Cache.start/0
(drab) lib/drab/supervisor.ex:26: Drab.Supervisor.start/2
(kernel) application_master.erl:273: :application_master.start_it_old/4
[info] Application phoenix_live_reload exited: :stopped
[info] Application file_system exited: :stopped
[info] Application phoenix_html exited: :stopped
[info] Application cowboy exited: :stopped
[info] Application cowlib exited: :stopped
[info] Application ranch exited: :stopped
** (Mix) Could not start application drab: exited in: Drab.Supervisor.start(:normal, [])
** (EXIT) an exception was raised:
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:error, :bad_name}. This protocol is implemented for: Atom, BitString, Date, DateTime, Decimal, Ecto.Date, Ecto.DateTime, Ecto.Time, Float, Integer, List, NaiveDateTime, Postgrex.Copy, Postgrex.Query, Postgrex.Stream, Time, URI, Version, Version.Requirement
(elixir) /private/tmp/elixir-20170801-32483-1rf8an1/elixir-1.5.1/lib/elixir/lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir) /private/tmp/elixir-20170801-32483-1rf8an1/elixir-1.5.1/lib/elixir/lib/string/chars.ex:22: String.Chars.to_string/1
(drab) lib/drab/live/cache.ex:60: Drab.Live.Cache.cache_file/0
(drab) lib/drab/live/cache.ex:24: Drab.Live.Cache.start/0
(drab) lib/drab/supervisor.ex:26: Drab.Supervisor.start/2
(kernel) application_master.erl:273: :application_master.start_it_old/4
The problem appears to be coming from https://github.com/grych/drab/blob/master/lib/drab/live/cache.ex#L60, where the call to Drab.Config.app_name()
returns nil I believe, and then when it is piped into :code.priv_dir
it results in a {:error, :bad_name}
tuple being returned.
hello again, i need to render a template from the commander
ive read your code and it appears i should be able to use this in the commander
params = %{ beginaddress: beginaddress, endaddress: endaddress}
Drab.Template.render_template("customer_session.html.eex", params )
to render the file located in /priv/templates/drab/customer_session.html.eex
however i get an error no function clause matching in EEx.eval_file/3 but i am only passing 2 args is this correct use of this fun
also do you have an idea how i can make brunch copy my drab templates from /web/static/templates/drab to /priv/templates/drab
After pushing a piece of html with drab attributes:
insert_html socket, "body", :beforeend, "<button drab-click='clicked'>aaa</button>"
Drab should parse that and enable corresponding events.
Is it possible to have a separate gen server that then triggers an update for all connected clients using drab?
I have a GenServer that polls for data / transforms and then stores the results, what I want is to show a live feed of the resulting data whenever it changes in all the connected browsers.
What I had now is a recursive loop with a sleep in the drab method, ex:
defp table_loop(socket) do
:timer.sleep(2000)
data = ExpensiveData.get_current
socket |> update!(:html, set: "<p>data<p>", on: ".table")
table_loop(socket)
end
It works but I wonder if there is a better way to do it. I would rather setup a GenEvent and let the GenServer send a notification that the data has changed so I can display it. This means I don't have to continually poll for updates like I do now in the above function.
Regards
I'm reading drab's source, and just noticed two or three places were we have String.to_atom
mostly on Drab.do_handle_cast
to convert the event_name into a function name. Would it be better to have String.to_existing_atom
as I guess most DOM events wont likely change, and there's just a handfull of the most commonly used ones. So if you are interested, I could provide a PR just to use to_existing_atom
and have peace of mind.
Thanks for drab again!
Hi @grych I'm sorry for bothering again but unfortunately I'm encountering another client-side issue😞
I think it is related to my Webpack setup but as it is a pretty barebones and common configuration I'm opening this issue to make sure that there is no big problem underlying it. I hope indeed that it is another of my silly mistake 😅
Here's the error that I get from the console:
My configuration is the following:
Elixir 1.4.5
Phoenix 1.3.rc2
Drab 0.5.0
The code of the whole application can be found here
Please let me know if I can help you in some way 😄
Thanks again for all your commitment! 🤗
This is a outstanding idea/project.
How do you handle security ? Can we/should we mix this with a token by each call to the server ?
So far, only working testing environment is a chromedriver.
Seems like phantomjs does not evaluate the javascripts, like connecting to the websockets. Needs more investigation.
Hey there, I'm new to Elixir and Phoenix. When I saw this amazing library I had to check it out. I've built a basic calculator to play around with the features of drab. My local version works fine :)
Then I wanted to deploy it to Heroku (https://drab-calculator.herokuapp.com/) but I get the following error and I can't figure out what's special on Heroku.
2017-08-20T12:43:27.938626+00:00 app[web.1]: ** (RuntimeError) Ampere "geydcobvga2do" can't be found in Drab Cache.
2017-08-20T12:43:27.938627+00:00 app[web.1]: (drab) lib/drab/live.ex:371: anonymous fn/6 in Drab.Live.do_poke/5
2017-08-20T12:43:27.938628+00:00 app[web.1]: (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
2017-08-20T12:43:27.938629+00:00 app[web.1]: (drab) lib/drab/live.ex:309: Drab.Live.do_poke/5
2017-08-20T12:43:27.938630+00:00 app[web.1]: (drab) lib/drab.ex:249: anonymous fn/6 in Drab.handle_event/6
Any help and hints appreciated :)
Replace the call to function_exists?
in Drab.check_handler_existence!/2
with function_exported?(commander_module, String.to_existing_atom(handler), 2)
The following will now work https://github.com/grych/drab/blob/master/lib/drab.ex#L281 for a release. To fix this, replace with:
"rebel.handler_error.#{Atom.to_string(env())}.js",
and add this to the module.
@env Mix.env
def env, do: @env
Under 1.5.0, when using nested renders:
<%= render("users.html", users: @users, count: @count, color: @color) %>
<%= for user <- @users do %>
<span @style.backgroundColor=<%=@color%>>
User of <%= @count %>: <%= render("user.html", user: user) %><br>
</span>
<% end %>
the EEx engine generates too many opening partials (<span drab-partial=>
) without closing them.
Please do not use 1.5.0 until it is resolved.
I had a problem with retrieving the value of radio buttons being reported correctly in the returned default form map. Since there are multiple inputs with the same name, the value of the last radio button is returned. The following should fix this.
function default_payload(sender, event) {
var params = {}
var form = closest(sender, function(el) {
return el.nodeName == "FORM"
})
if (form) {
var inputs = form.querySelectorAll("input, textarea, select")
var i = 0
inputs.forEach(function(input) {
var key = input.name || input.id || false
if (key) {
if (input.type == "radio") {
if (input.checked) {
params[key] = input.value
}
} else {
params[key] = input.value
}
}
})
}
return { ...
Broadcast send the messages only to the clients connected to the same server.
Trying to get phoenix 1.3-rc2 to work with latest drap
=INFO REPORT==== 12-Jun-2017::16:33:40 ===
application: logger
exited: stopped
type: temporary
** (Mix) Could not start application drab: exited in: DrabTestApp.start(:normal, [])
** (EXIT) an exception was raised:
** (UndefinedFunctionError) function DrabTestApp.start/2 is undefined (module DrabTestApp is not available)
DrabTestApp.start(:normal, [])
(kernel) application_master.erl:273: :application_master.start_it_old/4
Seems to me your mix.exs file contains [mod: {DrabTestApp}]
when it shouldn't
<%= if true do %>
<textarea>heading <%= @assign %> trailing</textarea>
<% end %>
Drab is modular. You mAy choose in https://tg.pl/drab/docs
In general, Drab treats the client browser as a database prOvider in https://tg.pl/drab
attempting to delete via
socket |> delete(:button, from: "#items" )
results in an error of no method delete with arity/3
thanks for the lib
<button drab-event="click" drab-hander="uppercase">Do it</button>
should be
<button drab-event="click" drab-handler="uppercase">Do it</button>
Getting these warnings on every single request:
[warn] :max_age was not set on Phoenix.Token.verify/4. A max_age is recommended otherwise tokens are forever valid. Please set it to the amount of seconds the token is valid, such as 86400 (1 day)
In significant amounts. A max_age should be specified to something to prevent reply attacks later. :-)
Overall in Drab.Core
or Drab
and links there from all broadcasting functions.
Hello
You got small typo error
WARNING: launch_event() is depreciated. Please use run_handler() instrad.
Works perfectly fine in development. Deployed to production and it's crashing. Any clue what that might be about?
Here's stack trace:
23:20:46.844 request_id=tj62j2os7fdkg3lu8h5vurldr0mq1ol2 [info] GET /tours/1-can-am-tour-of-2016
23:20:46.899 request_id=tj62j2os7fdkg3lu8h5vurldr0mq1ol2 [info] Sent 200 in 55ms
23:20:47.462 [info] JOIN "__drab:same_path:/tours/1-can-am-tour-of-2016" to Drab.Channel
Transport: Phoenix.Transports.WebSocket (2.0.0)
Serializer: Phoenix.Transports.V2.WebSocketSerializer
Parameters: %{}
23:20:47.463 [info] Replied __drab:same_path:/tours/1-can-am-tour-of-2016 :ok
23:20:49.726 [error] Drab Handler failed with the following exception:
** (RuntimeError) Can't find the expression or hash "ha2dimzxguzto" in the Drab.Live.Cache
(drab) lib/drab/live/cache.ex:40: Drab.Live.Cache.get/1
(drab) lib/drab/live.ex:298: anonymous fn/6 in Drab.Live.do_poke/5
(elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
(drab) lib/drab/live.ex:295: Drab.Live.do_poke/5
(drab) lib/drab.ex:249: anonymous fn/6 in Drab.handle_event/6
23:20:54.729 [error] Error in process #PID<0.2708.0> on node :"[email protected]" with exit value:
{{:badmatch, {:error, "timed out after 5000 ms."}},
[{Drab, :failed, 2, [file: 'lib/drab.ex', line: 286]},
{Drab, :"-handle_event/6-fun-3-", 6, [file: 'lib/drab.ex', line: 258]}]}
23:20:54.729 [error] Drab Process #PID<0.2708.0> died because of {:badmatch, {:error, "timed out after 5000 ms."}}
(drab) lib/drab.ex:286: Drab.failed/2
(drab) lib/drab.ex:258: anonymous fn/6 in Drab.handle_event/6
EDIT:
I dug around code a bit and found this: https://github.com/grych/drab/blob/master/lib/drab/live/cache.ex#L37
When running that line on the build server (same as prod) I get expected structure (template as a function thing). I don't even know how that method would produce output other than [{k: v}]
or []
Is there any other way to debug this in running production?
EDIT 2:
iex([email protected])7> :dets.info("priv/hashes_expressions.drab.cache.prod")
[type: :set, keypos: 1, size: 0, file_size: 5464,
filename: "priv/hashes_expressions.drab.cache.prod"]
iex([email protected])11> :dets.lookup("priv/hashes_expressions.drab.cache.prod", "ha2dimzxguzto")
{:error, {:file_error, "priv/hashes_expressions.drab.cache.prod", :enoent}}
Huh. Cache file is there but has no data in it?
Locally, however:
iex(2)> :dets.info("priv/hashes_expressions.drab.cache.prod")
[type: :set, keypos: 1, size: 13, file_size: 34266,
filename: "priv/hashes_expressions.drab.cache.prod"]
iex(1)> :dets.lookup("priv/hashes_expressions.drab.cache.prod", "ha2dimzxguzto")
[{"ha2dimzxguzto",
{:expr, ...
I triple-checked that same file is locally and one deployed:
live:
-rw-rw-r-- 1 deploy deploy 34266 Aug 10 00:47 hashes_expressions.drab.cache.prod
local:
-rw-r--r-- 1 oleg oleg 34266 Aug 9 17:42 hashes_expressions.drab.cache.prod
EDIT 3:
Ok, now I'm really confused. I thought maybe I'm looking at the wrong thing and there's 5464 byte cache file somewhere on production. Nopes:
deploy@loaded-bike-app:~/loaded_bike$ find ~/ -name hashes_expressions.drab.cache.prod | xargs du -sh
36K /home/deploy/builds/priv/hashes_expressions.drab.cache.prod
36K /home/deploy/builds/_build/prod/lib/loaded_bike/priv/hashes_expressions.drab.cache.prod
36K /home/deploy/builds/_build/prod/rel/loaded_bike/lib/loaded_bike-0.0.1+205-f804a9b/priv/hashes_expressions.drab.cache.prod
36K /home/deploy/loaded_bike/lib/loaded_bike-0.0.1+205-f804a9b/priv/hashes_expressions.drab.cache.prod
EDIT 4:
Ah, so prod doesn't even see the file in the first place (local returns true
)
iex([email protected])6> :dets.is_dets_file("priv/hashes_expressions.drab.cache.prod")
{:error, {:file_error, "priv/hashes_expressions.drab.cache.prod", :enoent}}
EDIT 5:
Ok, I figured it out. App was looking to the cache file in the non-existant folder:
~/loaded_bike/priv
and not in where the lib actually is:
~/loaded_bike/lib/loaded_bike-0.0.1+205-f804a9b/priv
Seems like a configuration issue. But there's no issues serving files from priv/static
???
Observe:
def an_event_handler(socket, _dom_sender) do
spawn_link fn -> looop(socket) end
end
defp looop(socket) do
:timer.sleep(5000)
IO.puts "looop #{self() |> inspect}"
looop(socket)
end
This process is not being killed when Drab is down (like leaving the page, reload, etc).
Possible solution: rethink Drab's processes.
Drab is a gen server, created in the Channel, on join. It waits for a messages coming from the Channel. On event handler request, it spawn_link
calling the Handler in the Commander.
Closing the socket (navigate away, closing the browse) does kill the Handler, but not the processes created with spawn_link
by the handler.
Possible solution: create a simple-one-by-one
process tree, instead of spawning processes with spawn_link
.
It's a bit odd one for me. Let's assume we have this:
render(conn, "show.html", some_value: "my value")
If I don't somehow use that assign inside the template it will not be available for commander's Drab.Live.peek(socket, :some_value)
Work-around is to include it like this (even it does absolutely nothing in the template):
<% @some_value # don't remove or drab will complain %>
Hi,
in your example it says
params: Map %{name|id => value} of all inputs, selects, etc which are in the alert box
for modals.
I´m not sure if drag might to solve what i´m looking for, but at the moment i submit the forms in phoenix and get the params in the controller (new for example). I´d like to get rid of the page refresh and instead send the form to a commander function. I can´t query every input by id or class cause the form is dynamically generated.
Is this possible?
thanks
Realised that I left that code untested and #52 crashed everything in production.
Assuming I have simple commander like this:
defmodule FooCommander do
use Drab.Commander
def pres_buton(socket, _sender) do
Drab.Live.poke(socket, some_variable: "new value")
end
end
How can I test this code? Mainly to ensure nothing is crashing there.
Thanks!
Hello, we think we've found a bug.
We have a regular phoenix project and we configured Drab, to minimize JS, everything works fine as long as you try with Chrome or Firefox, but Safari emits two errors whenever you change from one page to another (http://localhost:4000/users >> http://localhost:4000/users/1/edit)
1º WebSocket connection to 'ws://localhost:4000/phoenix/live_reload/socket/websocket?vsn=1.0.0' failed: WebSocket is closed due to suspension.
2º WebSocket connection to 'ws://localhost:4000/socket/websocket?__drab_return=SFMyNTY.g3QAAAACZAAEZGF0YWwAAAADaAJkAAxfX2NvbnRyb2xsZXJkACJFbGl4aXIuUGhhbnRhd2ViLkFBLlVzZXJDb250cm9sbGVyaAJkAAhfX2FjdGlvbmQABWluZGV4aAJkAAlfX2Fzc2lnbnNqamQABnNpZ25lZG4GADU68h9bAQ.KdcBNzKYoJw-qOXZpFp24hwh0LHuMtM6mb1X9TuxTJo&vsn=1.0.0' failed: WebSocket is closed due to suspension.
It doesn't if you just reload page.
This happens in both iOS & OSX, but iOS is critical, because Safari crashes on our old iPAD, you just navigate between pages with some simple commanders and it crashes.
We haven't investigate more, we try to help you, because this seems a critical one, at least for us.
Greetings
Reported by Eirik Anfinsen by email:
An issue with a changing csfr-token and reset of input fields on poke. Here's some sample code that you could use to recreate and debug:
Controller:
def drab_example(conn, _params) do
render conn, "drab_example.html", list: ["From controller", "Also from controller"]
end
Template (drab_example.html.drab):
<h1>Sample form</h1>
<%= form_for @conn, page_path(@conn, :drab_example), [as: :drab], fn f -> %>
<%= text_input f, :text, placeholder: "Reset after poke" %>
<br /><br />
<%= text_input f, :new_item, placeholder: "Random list item" %>
<button type="button" drab-click="add_item">Add</button>
<h2>List</h2>
<ul>
<%= for(item <- @list) do %>
<li><%= item %></li>
<% end %>
</ul>
<%= submit "Submit" %>
<% end %>
Commander:
def add_item(socket, sender) do
items = socket |> peek(:list)
new_item = socket |> Drab.Query.select(:val, from: "#drab_new_item")
new_list = items ++ ["#{new_item}"]
Drab.Live.poke socket, list: new_list
end
As you (hopefully!) can see, peek/poke does its job, but when the ul-list is updated on poke, whatever you typed into the first text input is reset to the placeholder text.
What the example does not show, however, is that the form does not pass validation on submit when the protect_from_forgery plug is used. As far as we can tell, this reason for this is thatthe csfr-token generated on page load is changed after the poke.
This is the offending line: https://github.com/grych/drab/blob/master/lib/drab/live.ex#L306
I renamed ErrorHelpers and Drab started to explode. Wondering what's that all about.
Hey, i'm trying to update an assign, for example @imgname, used within Phoenix.View.render_to_string("img.html", imgname: imgname), inserted with insert_html after a successful upload.
I then have a form to change the @imgname, with a button to Confirm, but it only updates the assign used in the index.html.
Is this expected behavior or should it update?
I've also experienced the issue #34.
Thanks for developing drab! All the best.
Not a Bug/issue.
Trying to update the values on a linked Select
This doesn't work.
socket
|> update(:val, set: ["One","Two"], on: "#instance_deployment_data_center")
Please how best can i achieve this?
While trying to run tests, I encounter the following error:
** (ExUnit.DuplicateDescribeError) describe "Drab.Query delete" is already defined in DrabTestApp.QueryTest
test/integration/query_test.exs:404: (module)
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/code.ex:376: Code.require_file/2
(elixir) lib/kernel/parallel_require.ex:59: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5
Although I'm on Elixir 1.5, this does not seem to be related to #31 .
Any idea what I might be doing wrong?
MacOS 10.12.6
Firefox 55.0.2
On the demo page, when clicking the Upcase
and Downcase
buttons, a page reload is initiated (and the text is not changed). All other demos appear to be working.
These actions work in Safari.
Say I have just created a new Phoenix 1.3 project with Drab v0.5.4 and have generated an Accounts
context with the following user.ex
struct:
schema "users" do
field :email, :string
field :name, :string
timestamps()
end
In a corresponding user_controller.ex
, I have a standard index
action that renders the index.html.drab
template, passing it a users
assigns which is a list of users:
def index(conn, _params) do
users = Accounts.list_users()
changeset = Accounts.change_user(%User{})
render(conn, "index.html", users: users, changeset: changeset)
end
The index.html.drab
displays the list of users in the users
assigns inside a <tbody>
element, rendering a new <tr>
for each user in the users
assigns, and new <td>
for both the name
and email
fields on that user:
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody>
<%= for user <- @users do %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td class="text-right">
<span><%= link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs" %></span>
<span><%= link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs" %></span>
<span><%= link "Delete", to: user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= render "form.html", Map.put(assigns, :action, user_path(@conn, :create)) %>
To demonstrate the issue, notice the call to render a form to create a new user on the same index.html.drab
template above on the last line of the snippet.
Here is the form.html.eex
template that is rendered within the index.html.drab
template:
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :name, class: "control-label" %>
<%= text_input f, :name, class: "form-control" %>
<%= error_tag f, :name %>
</div>
<div class="form-group">
<%= label f, :email, class: "control-label" %>
<%= text_input f, :email, class: "form-control" %>
<%= error_tag f, :email %>
</div>
<div class="form-group" drab-click="create">
<button type="button">
Submit
</button>
</div>
<% end %>
I've placed a drab-click
on the button in the form to hit the create
function in the user_commander.ex
Drab commander, which will create the user using the name and email from the form, and then poke
the users
assigns that is used to render the table of users with the new list of users:
def create(socket, sender) do
user_params = sender.params["user"]
case DrabTest.Accounts.create_user(user_params) do
{:ok, user} ->
users = DrabTest.Accounts.list_users()
poke socket, users: users
{:error, %Ecto.Changeset{} = changeset} ->
poke socket, test_text: "Error. Could not create user."
end
end
However, when I click the Submit button, the new user is created successfully, but the existing <tbody>
is not updated, and instead, the new list of users poked in the users
assigns is rendered above the containing <table>
element in a <span drab-ampere='...'>
element, without the <tr>
and <td>
elements. Here is a snippet of the resulting HTML:
<span drab-partial="haztsnbvhe3ta">
<h2>Listing Users</h2>
<span drab-ampere="gqzdsnjqge3tm">
John Smith
[email protected]
<span><span drab-ampere="gi4domjtgiyds"><a class="btn btn-default btn-xs" href="/users/2">Show</a></span></span>
<span><span drab-ampere="giydcmzrhazde"><a class="btn btn-default btn-xs" href="/users/2/edit">Edit</a></span></span>
<span><span drab-ampere="geytgojtgq2deni"><a class="btn btn-danger btn-xs" data-confirm="Are you sure?" data-csrf="PCYcYAUaICkEJwsUO1Q1ARAvLwM6AAAAEAz1nxGDRbMLBgQFuly5wQ==" data-method="delete" data-to="/users/2" href="#" rel="nofollow">Delete</a></span></span>
Test User
[email protected]
<span><span drab-ampere="gi4domjtgiyds"><a class="btn btn-default btn-xs" href="/users/6">Show</a></span></span>
<span><span drab-ampere="giydcmzrhazde"><a class="btn btn-default btn-xs" href="/users/6/edit">Edit</a></span></span>
<span><span drab-ampere="geytgojtgq2deni"><a class="btn btn-danger btn-xs" data-confirm="Are you sure?" data-csrf="PCYcYAUaICkEJwsUO1Q1ARAvLwM6AAAAEAz1nxGDRbMLBgQFuly5wQ==" data-method="delete" data-to="/users/6" href="#" rel="nofollow">Delete</a></span></span>
</span><table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>John Smith</td>
<td>[email protected]</td>
<td class="text-right">
<span><span drab-ampere="gi4domjtgiyds"><a class="btn btn-default btn-xs" href="/users/2">Show</a></span></span>
<span><span drab-ampere="giydcmzrhazde"><a class="btn btn-default btn-xs" href="/users/2/edit">Edit</a></span></span>
<span><span drab-ampere="geytgojtgq2deni"><a class="btn btn-danger btn-xs" data-confirm="Are you sure?" data-csrf="ZVcBEnorQn1iTCtVBwABGwtvGSAzAAAAR8T+4S1IVgCzLx60j7TwbA==" data-method="delete" data-to="/users/2" href="#" rel="nofollow">Delete</a></span></span>
</td>
</tr>
</tbody>
</table>
What I would expect is for the poke of :users
to update the <tbody>
, not inject the updated :users
assigns in a <span>
above the <table>
element. I don't believe it is an issue with using Drab with for
inside a template, as the demo page shows a simple implementation of updating a list of users.
I've pasted some screenshots before and after creating a new user to provide a visual as well.
Hi Tomek,
First of all, thanks for creating Drab, I love the concept.
The issue I'm running into is a pretty simple use case where I'm attempting to favourite an item in the DOM, which updates a class from is-unselected
to is-selected
and also update a data attribute data-favourited
from false
to true
.
The element that calls Drab is:
%svg(class="o-card-favourite #{selected(@listing)}" length="29px" version="1.1" viewbox="30 29" width="30px", xmlns="http://www.w3.org/2000/svg" "xmlns:xlink"="http://www.w3.org/1999/xlink" id="favourite-#{@listing.id}" drab-click='toggle_favourite' data-listing="#{@listing.id}" data-user="#{@user.id}" data-favourited="#{length(@listing.users) > 0}"}
And the associated commander is:
def toggle_favourite(socket, dom_sender) do
user_id = dom_sender["data"]["user"]
listing_id = dom_sender["data"]["listing"]
favourited = dom_sender["data"]["favourited"]
if (favourited) do
unfavourite_listing(socket, dom_sender, user_id, listing_id)
else
favourite_listing(socket, dom_sender, user_id, listing_id)
end
end
defp favourite_listing(socket, dom_sender, user_id, listing_id) do
changeset = Favourite.changeset(%Favourite{}, %{user_id: user_id, listing_id: listing_id})
case Repo.insert(changeset) do
{:ok, _favourite} ->
socket
|> update(class: "is-unselected", set: "is-selected", on: this(dom_sender))
|> update(attr: "data-favourited", set: true, on: this(dom_sender))
{:error, changeset} ->
IO.puts dom_sender
end
end
defp unfavourite_listing(socket, dom_sender, user_id, listing_id) do
favourite = Repo.get_by(Favourite, %{user_id: user_id, listing_id: listing_id})
case Repo.delete(favourite) do
{:ok, struct} ->
socket
|> update(class: "is-selected", set: "is-unselected", on: this(dom_sender))
|> update(attr: "data-favourited", set: false, on: this(dom_sender))
{:error, changeset} ->
IO.puts dom_sender
end
end
Clicking the favourite icon once correctly updates the DOM according to the browser's DOM inspector (both the data attribute and the class are changed as they should), but clicking on the favourite icon once more (without a page reload) does not cause the dom_sender
map argument to update the data-favourited
property (it's value will be false, even when the DOM has been updated to true). This is not the case upon refreshing the browser. This occurs consistently whether the data-favourited
has been set to true or false and is not being updated in the attached dom_sender
argument, but is being updated in the DOM itself.
Any help is very much appreciated, please let me know if you need more information.
First I wanted to say thanks for the library. This looks like a great idea.
I was trying to implement a button and I noticed that the browser was sending 350k back to the server in the form of conn.assigns on an event, while I was expecting perhaps several hundred bytes if this was a normal javascript rest call.
I tried removing the assigns from drab.live.js:3, however, this caused the stacktrace:
[error] Drab Handler failed with the following exception:
** (BadMapError) expected a map, got: nil
(stdlib) :maps.find("gi2dimbsga4dg", nil)
(drab) lib/drab/live.ex:457: Drab.Live.assigns/3
(drab) lib/drab/live.ex:280: Drab.Live.do_poke/5
(drab) lib/drab.ex:249: anonymous fn/6 in Drab.handle_event/6
Is there a way to reduce the payload? Looking at lib/drab/live.ex, would it be necessary to have all the assigns from the page to update them? Thanks.
I could not find a support for tuning the sender["form"] map into a controller type params. Here is the solution I came up with. Might be a helper to add to Drab.
@doc """
Convert form parameters returned by Drab into controller type params
map.
# Examples
iex> UccChat.ServiceHelpers.normalize_params %{"_csrf" =>
...> "1234", "user[id]" => "42", "user[email]" => "[email protected]",
...> "user[account][id]" => "99", "user[account][address][street]" =>
...> "123 Any Street"}
%{"_csrf" => "1234",
"user" => %{"account" => %{"address" => %{"street" => "123 Any Street"},
"id" => "99"}, "email" => "[email protected]", "id" => "42"}}
"""
def normalize_params(params) do
Enum.reduce(params, "", fn {k, v}, acc ->
acc <> k <> "=" <> v <> "&"
end)
|> String.trim_trailing("&")
|> Plug.Conn.Query.decode()
end
Elixir 1.5.2
OTP 20.1
Phoenix 1.3 release
Error:
==> drab
Compiling 29 files (.ex)
15:14:25.037 [info] Compiling Drab Templates
warning: function Mix.Phoenix.copy_from/5 is undefined or private. Did you mean one of:
* copy_from/4
lib/mix/tasks/drab.gen.commander.ex:24
Generated drab app
Defined here: https://github.com/grych/drab/blob/master/lib/mix/tasks/drab.gen.commander.ex#L24
The definition of copy_from/4
is here: https://github.com/phoenixframework/phoenix/blob/master/lib/mix/phoenix.ex#L29
Thus trying to run mix drab.gen.commander
crashes. ^.^
<input value="<%= @assign %>">
Currently, you may poke
and peek
the assign, but when the user changes the value, peek
shows the original one. This is by design, and mentioned in docs.
Proposition: update every time user changes the value. It would be way more intuitive.
Any chance on making it capable of sharing an existing socket by adding a drab channel handler to it and passing some javascript code to the Drab.Client.js
call that can access the existing socket? Duplicate websockets are killed by certain corporate network setups. :-)
This library is set to work only with Phoenix 1.2 even though 1.3 is backwards compatible. The version restriction needs to be fixed up to support both.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.