Hi guys!
I try CSV Export with Phoenix and LiveView with this guide CSV Export with Phoenix and LiveView - Tutorials and screencasts for Elixir, Phoenix and LiveView (fullstackphoenix.com) .
I use filters for data displayed as a table on my heex:
<form phx-change=“filter” class=“pt-4 pb-4 text-sm”>
<span class=“inline-flex”>
<span>
<label>node:</label>
<%= Phoenix.HTML.Form.number_input(:options, :node,
value: @options.node ,
“phx-debounce”: “300”,
class: “text-sm border border-slate-300 text-slate-600 w-20 ml-2 mr-4”
) %>
</span>
<span>
<label>date:
<%= Phoenix.HTML.Form.date_input(:options, :date,
value: @options.date ,
class: “text-sm border border-slate-300 text-slate-600 ml-2 mr-4”
) %>
</span>
</span>
</form>
and link to export on the same heex:
<.link
href={~p"/export?#{%{message: “journals”, node: @options.node , date: @options.date , sort_by: @options.sort_by , sort_order: @options.sort_order }}“}
method=“post”
class=”-ml-px inline-flex items-center px-3 h-10 border border-slate-300 bg-white text-base font-medium text-slate-600 no-underline hover:bg-slate-300 rounded-md"
>
Export
</.link>
In export controller:
def create(conn, %{“message” => “journals”, “node” => node, “date” => date, “sort_by” => sort_by, “sort_order” => sort_order}) do
…
csv_data = …
conn
|> put_resp_content_type(“text/csv”)
|> put_resp_header(“content-disposition”, “attachment; filename="journals.csv"”)
|> put_root_layout(false)
|> send_resp(200, csv_data)
end
CSV export works fine, but i have some quiestion: the filter (form) stops responding to changes after loading csv-file, but all other links work (pagination and sorting).
It seems that “phx-change” don’t send any messages to my live_view.
I also read this topics, but could not find an answer to my question:
Thank you.
Valeriy:
<.link
href={~p"/export?#{%{message: “journals”, node: @options.node , date: @options.date , sort_by: @options.sort_by , sort_order: @options.sort_order }}“}
method=“post”
…>
Export
</.link>
Off the top of my head, I suspect what’s going on here is that the LiveView socket disconnects on export since clicking a default generated <.link href=...>
results in traditional browser navigation. You can check if this is the case by using the phx-disconnected
binding to show/do something.
If that is the case, you could try specifying the anchor tag’s download
attribute and/or target
attribute as new tab and see if that makes a difference.
Updated to add:
opened 07:21PM - 23 Mar 23 UTC
closed 06:46PM - 12 May 23 UTC
### Environment
* Elixir version (elixir -v):
```
Erlang/OTP 25 [erts-13.2… ] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
Elixir 1.14.3 (compiled with Erlang/OTP 25)
```
* Phoenix version (mix deps):
```
phoenix 1.6.16 (Hex package) (mix)
locked at 1.6.16 (phoenix) e15989ff
```
* Phoenix LiveView version (mix deps):
```
phoenix_live_view 0.18.18 (Hex package) (mix)
locked at 0.18.18 (phoenix_live_view) a5810d04
```
* Operating system:
```
MacOS Ventura
```
* Browsers you attempted to reproduce this bug on (the more the merrier):
```
Safari
Firefox
```
* Does the problem persist after removing "assets/node_modules" and trying again? Yes/no:
```
Yes
```
### Actual behavior
The LiveView disconnects when clicking a download link, e.g.
```heex
<a href="google.com" download="google.html">Download Google</a>
```
Causes the LiveView to disconnect in a manner similar to #2542:
```
destroyed: the child has been removed from the parent
```
### Expected behavior
Interacting with the link does not cause LiveView to disconnect.
Thank you codeanpeace !
target=“_blank” works fine, but this causes the opening of a blank page for a short time. It seems not very nice.
I couldn’t get download attribute to work. I try this:
<.link
href={~p"/export?#{%{message: “journals”, node: @options.node , date: @options.date , sort_by: @options.sort_by , sort_order: @options.sort_order }}“}
method=“post”
download
…>
Export
</.link>
or
<.link
href={~p"/export?#{%{message: “journals”, node: @options.node , date: @options.date , sort_by: @options.sort_by , sort_order: @options.sort_order }}“}
method=“post”
download = “journals.csv”
…>
Export
</.link>
Do you have any thoughts on this?
Hmm, could you elaborate a bit more? Are you seeing an error? Does no download take place? What does the rendered html of the a
anchor tag look like?
The MDN docs list linked above mentions some requirements for the download
attribute for anchor elements and different browsers may handle things a bit differently as well.
codeanpeace:
Are you seeing an error?
As I can see, there are no errors.
Dev-tools console output:
…
phx-F46Bx0s-9TzePR4i socket: disconnect for page nav - undefined
phx-F46Bx0s-9TzePR4i destroyed: the child has been removed from the parent - undefined
destroyed: the child has been removed from the parent - undefined
Yes, it works well.
HTML code for:
<.link
href={~p"/export?#{%{message: “journals”, node: @options.node , date: @options.date , sort_by: @options.sort_by , sort_order: @options.sort_order }}“}
method=“post”
download
…>
Export
</.link>
looks like this:
<a href=“/export?message=journals&sort_by=date&sort_order=desc&node=3&date=” data-method=“post” data-csrf=“DHpdChIqN3AaPQUjBSdfCSIaWRknNnF64IeXjx_IlUABDkfXmJtNsP6-” data-to=“/export?message=journals&sort_by=date&sort_order=desc&node=3&date=” class=“-ml-px inline-flex items-center px-3 h-10 border border-slate-300 bg-white text-base font-medium text-slate-600 no-underline hover:bg-slate-300 rounded-md” download=“”>
Export
</a>
I try it in two browsers: edge and chrome, the behavior in both cases is the same.
P.S. Just in case:
mix phx.new --version
Phoenix installer v1.7.2
elixir --version
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
Elixir 1.15.4 (compiled with Erlang/OTP 26)
That’s odd, LiveView shouldn’t disconnect when the download
attribute as of this PR: Fix for #2552: do not unload LiveView socket on `download` link clicks. by sherbondy · Pull Request #2611 · phoenixframework/phoenix_live_view · GitHub
I’d suggest taking a few steps back and directly specifying the anchor tag with the download attribute temporarily just to see if that changes anything and get it working as expected.
<a href="/export?message=journals&sort_by=date&sort_order=desc&node=3&date=" download>
Export
</a>
I try this:
<a
href=“/export?message=journals&sort_by=date&date=&sort_order=desc&node=”
data-method=“post”
data-to=“/export?message=journals&sort_by=date&date=&sort_order=desc&node=”
data-csrf=“xxx”
class=“-ml-px inline-flex items-center px-3 h-10 border border-slate-300 bg-white text-base font-medium text-slate-600 no-underline hover:bg-slate-300 rounded-md”
download
>
Export
</a>
The behavior is the same:
phx-F47RLs3Fj_DdkRFB socket: disconnect for page nav - undefined
phx-F47RLs3Fj_DdkRFB destroyed: the child has been removed from the parent - undefined
destroyed: the child has been removed from the parent - undefined
Perhaps some other settings are required somewhere else?
That’s odd, LiveView shouldn’t disconnect when the download
attribute as of this PR: Fix for #2552: do not unload LiveView socket on download
link clicks. by sherbondy · Pull Request #2611 · phoenixframework/phoenix_live_view · GitHub
I find this code in phoenix_live_view/assets/js/phoenix_live_view/dom.js:
wantsNewTab(e){
let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button === 1)
let isDownload = (e.target instanceof HTMLAnchorElement && e.target.hasAttribute(“download”))
let isTargetBlank = e.target.hasAttribute(“target”) && e.target.getAttribute(“target”).toLowerCase() === “_blank”
return wantsNewTab || isTargetBlank || isDownload
},
I additionally tried simple GET (not POST):
<a
href=“/export?message=journals&sort_by=date&date=&sort_order=desc&node=”
download
>
Export
</a>
and got the same result: download file works well, but “socket: disconnect for page nav” and “destroyed: the child has been removed from the parent”.
wantsNewTab(e){
let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button === 1)
let isDownload = (e.target instanceof HTMLAnchorElement && e.target.hasAttribute(“download”))
let isTargetBlank = e.target.hasAttribute(“target”) && e.target.getAttribute(“target”).toLowerCase() === “_blank”
return wantsNewTab || isTargetBlank || isDownload
},
BTW (for FF and chrome):
e.ctrlKey - don’t work in my case
e.shiftKey - works, but open a blank page
download - don’t work in my case
target=“_blank” - works, but open a blank page for a short time