Giter Site home page Giter Site logo

grych / drab Goto Github PK

View Code? Open in Web Editor NEW
867.0 37.0 43.0 2.57 MB

Remote controlled frontend framework for Phoenix.

Home Page: https://tg.pl/drab

License: MIT License

JavaScript 6.91% Elixir 89.43% HTML 3.29% CSS 0.01% Shell 0.36%
phoenix elixir elixir-phoenix phoenix-channels dom elixir-lang elixir-programming-language web-application web-framework websocket

drab's People

Contributors

batou99 avatar bdfinlayson avatar dgmcguire avatar gonzooo avatar grych avatar guidotripaldi avatar jpinnix avatar overminddl1 avatar robobakery avatar suexcxine avatar vic avatar x4lldux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

drab's Issues

Javascript is not ES5, yet is not transpiled down

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. ^.^;

checkbox value not reported correctly in sender["form"]

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')

mix drab.gen.commander Name error on version 0.4.1

** (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

Drab.Config will probably fail with releases

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

Add key_code value as part of dom_sender map

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

Slow build times

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.

Drab doesn't work for umbrella projects

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.

how to render a template from the commander

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

Drab Manually publish to socket

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

Leaking atoms.

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!

Drab + Webpack + React, "require is not defined"

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:

screen shot 2017-07-24 at 13 51 24

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! 🤗

Security concerns ?

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 ?

Drab on heroku

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 :)

use Kernel.function_exported?/3

Replace the call to function_exists? in Drab.check_handler_existence!/2 with function_exported?(commander_module, String.to_existing_atom(handler), 2)

Compatibility with Elixir 1.5.0

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.

checked radio buttons not reported correctly in sender["form"]

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 { ...

Compatibility with Phoenix 1.3

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

error with delete

attempting to delete via
socket |> delete(:button, from: "#items" )
results in an error of no method delete with arity/3

typo in example

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>

Warnings on every single request

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. :-)

Can't find the expression or hash "ha2dimzxguzto" in the Drab.Live.Cache

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 ???

Process created in a handler or callback is orphaned after Drab shutdown

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.

Assign @some_value not found in Drab EEx template

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 %>

map from form inputs, selects

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

How to test Commander code in my app

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!

Safari troubles

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

Changing assigns inside form_for resets input values

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.

Controller Assign doesn't update if inserted with render_to_string

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.

Update Select Values

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?

Can not run tests

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?

Bug: Update of assigns used in for loop to create rows in a <table> does not work properly

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.

Before Creating Test User

before creating test user

After Creating Test User

after creating test user

Updated data attribute not showing in dom_sender

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.

Drab websocket frame size / performance

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.

Add a params parser

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

Drab compile error for the mix task

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. ^.^

Share socket

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. :-)

Phoenix 1.3

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.