Download or Export File from Phoenix 1.7 LiveView

Hello!
I’m trying to use CSV to give LiveView users a downloadable report. I am getting confused by all the possibly outdated(?) info I’m finding here and elsewhere. I’m too new to Elixir and Phoenix to understand.
So far, I have a LiveView where the user uploads a CSV. I then process that and add some data, ending up with a list of maps like:

[
  %{
    "Carrier" => "T-Mobile",
    "Country" => "USA",
    "Email" => "",
    "FirstName" => "Test",
    "LastName" => "",
    "MobileNo" => "12345678910",
    "Status" => "Active",
    "TemplateID" => "123",
    "UniqueID" => "1234567"
  },
...
]

I have a controller for the download:

defmodule JHWeb.ExportController do
  use JHWeb, :controller

  def create(conn, %{"download_data" => data}) do
    file = data
    |> CSV.encode()
    |> Enum.to_list()
    |> to_string()

    conn
    |> put_resp_content_type("text/csv")
    |> put_resp_header("content-disposition", "attachment; filename=\"download.csv\"")
    |> put_root_layout(false)
    |> send_resp(200, file)
  end
end

which is obviously cobbled together from code examples I could find. I have also tried:

file = File.open!("download.csv", [:write, :utf8])
data |> CSV.encode |> Enum.each(&IO.write(file, &1))

per the CSV documentation, but it also fails.

Finally, I can’t figure out what I’m supposed to do in the LiveView. I have a button:

<button :if={@download_data} phx-click="download_csv"
          phx-disable-with="Processing..."
 >Download CSV</button>

with an event handler:

def handle_event("download_csv", _params, socket) do

    # Get Base URL
    uri = socket.assigns.uri
    base_url = uri.scheme <> "://" <> uri.authority

    export_resp = Req.post!("#{base_url}/api/v1/export", json: %{download_data: socket.assigns.download_data})
 
    {:noreply, socket}
  end

I get a 500 error:

(Protocol.UndefinedError) protocol String.Chars not implemented for {“Carrier”, “T-Mobile”} of type Tuple

I don’t have enough of a mental model or frame of reference to know what I’m doing wrong, what the correct way is to do downloads from a LiveView, or how to proceed in general. Help is greatly appreciated. :slight_smile:

You dont need to create the csv file but what I think you do wrong is pass in a map. I think it needs to be a list of lists:

data
|> Enum.map(&Map.value/1)
|> CSV.encode()
...
1 Like

You cannot trigger a download through websockets / LV. You’d need to redirect the user to the url where the file is served. There’s also hacks e.g. using iframes to trigger the download without breaking the running LV page.

Indeed. You typically just want to link to a controller for that (you can use something like <a download href="/csv_export?download_data=xyz" to avoid the user navigating away from your LiveView or opening a new tab).

1 Like