How to Read a Csv File in Phoenix
A common need in web apps is to consign information to dissimilar file formats. Ane common, or even maybe the most common format, is exporting CSV files. CSV stands for comma separated values and its basically just a text file where all the values are separated by comma or semi colon. A CSV file is in turn easy to import in other programs or can easily be parsed with a simple script.
In this tutorial I desire to show how to export a CSV from an Elixir Phoenix awarding. And I want to export the CSV from a Phoenix LiveView interface where I tin can pick what fields to export.
I already take a page that consists of a table with customers. The headers are name, accost, zip, metropolis, phone, longitude and latitude. The goal is to add an export-CSV functionality that can optionally pick and choose the attributes.
I will implement the export CSV functionality in these steps
- Add together CSV library to mix file
- Create a controller for the export
- Add export CSV route to the routes file
- Add the Export push button and examination the solution
Add CSV library to mix file
The are a few CSV libraries that tin to this only I went with https://hexdocs.pm/csv/CSV.html. So, I will add information technology do the deps and install the dependencies.
# mix.exs defp deps practice [ # other deps {:csv, "~> 2.4"}, ] end
And run:
mix deps.go
Now that the CSV library is installed, I can movement on to building out the logic to export the CSV.
Create a controller for the export
Since the goal is to generate a file that needs to be downloaded and not displayed in the browser I need to customise the conn pipeline and specify the content type equally text/csv
. I will also need to specify the response header: "content-disposition", "attachment; filename=\"consign.csv\""
.
# lib/tutorial_web/controllers/export_controller.ex defmodule TutorialWeb.ExportController do use TutorialWeb, :controller alias Tutorial.Customers def create(conn, _params) do fields = [:proper name, :address, :zip, :metropolis, :phone, :longitude, :latitude] csv_data = csv_content(Customers.list_customers(), fields) conn |> put_resp_content_type("text/csv") |> put_resp_header("content-disposition", "attachment; filename=\"export.csv\"") |> put_root_layout(false) |> send_resp(200, csv_data) end defp csv_content(records, fields) do records |> Enum.map(fn record -> record |> Map.from_struct() |> Map.take([]) # gives an empty map |> Map.merge( Map.accept(record, fields) ) |> Map.values() end) |> CSV.encode() |> Enum.to_list() |> to_string() end finish
Annotation that I have hard coded the fields for now. Further downwardly, I will replace the hard coded fields to something that comes from the params and is user configured.
Add export CSV route to the routes file
Non that the controller is in place, I can add a port route to it:
# lib/tutorial_web/router.ex scope "/", TutorialWeb do pipe_through :browser # other routes postal service "/export", ExportController, :create end
Add the Export button and test the solution
To test this, I can just add together a normal link to the export folio and make information technology have use post instead of get past adding method: :post
pick.
<!-- lib/tutorial_web/alive/customer_live/index.html.heex --> <div class="flex justify-end mb-2"> <%= link to: Routes.export_path(@socket, :create), class: "btn btn-secondary", method: :post exercise %> Export <svg xmlns="http://www.w3.org/2000/svg" fill up="currentColor" class="w-6 h-vi ml-1" viewBox="0 0 sixteen sixteen"> <path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 ane.5 0 0 one nine.5 3V1H4a1 1 0 0 0-ane 1v9H2V2a2 2 0 0 1 2-2h5.5L14 iv.5ZM3.517 14.841a1.xiii 1.xiii 0 0 0 .401.823c.13.108.289.192.478.252.19.061.411.091.665.091.338 0 .624-.053.859-.158.236-.105.416-.252.539-.44.125-.189.187-.408.187-.656 0-.224-.045-.41-.134-.56a1.001 1.001 0 0 0-.375-.357 2.027 2.027 0 0 0-.566-.21l-.621-.144a.97.97 0 0 one-.404-.176.37.37 0 0 i-.144-.299c0-.156.062-.284.185-.384.125-.101.296-.152.512-.152.143 0 .266.023.37.068a.624.624 0 0 ane .246.181.56.56 0 0 1 .12.258h.75a1.092 ane.092 0 0 0-.2-.566 ane.21 1.21 0 0 0-.5-.41 1.813 ane.813 0 0 0-.78-.152c-.293 0-.551.05-.776.15-.225.099-.4.24-.527.421-.127.182-.19.395-.19.639 0 .201.04.376.122.524.082.149.ii.27.352.367.152.095.332.167.539.213l.618.144c.207.049.361.113.463.193a.387.387 0 0 1 .152.326.505.505 0 0 1-.085.29.559.559 0 0 1-.255.193c-.111.047-.249.07-.413.07-.117 0-.223-.013-.32-.04a.838.838 0 0 1-.248-.115.578.578 0 0 i-.255-.384h-.765ZM.806 13.693c0-.248.034-.46.102-.633a.868.868 0 0 i .302-.399.814.814 0 0 1 .475-.137c.15 0 .283.032.398.097a.7.7 0 0 1 .272.26.85.85 0 0 1 .12.381h.765v-.072a1.33 1.33 0 0 0-.466-.964 1.441 1.441 0 0 0-.489-.272 i.838 i.838 0 0 0-.606-.097c-.356 0-.66.074-.911.223-.25.148-.44.359-.572.632-.13.274-.196.half dozen-.196.979v.498c0 .379.064.704.193.976.131.271.322.48.572.626.25.145.554.217.914.217.293 0 .554-.055.785-.164.23-.11.414-.26.55-.454a1.27 1.27 0 0 0 .226-.674v-.076h-.764a.799.799 0 0 1-.118.363.seven.vii 0 0 i-.272.25.874.874 0 0 ane-.401.087.845.845 0 0 1-.478-.132.833.833 0 0 1-.299-.392 1.699 ane.699 0 0 one-.102-.627v-.495Zm8.239 2.238h-.953l-1.338-3.999h.917l.896 3.138h.038l.888-3.138h.879l-ane.327 4Z"/> </svg> <% end %> </div>
With that on place, when I click the the download file dialog window now opens and I tin download the csv file.
Adding the select columns functionality
With that in identify and working I want to have thing a stride further. I neat thing now would be to be able to select the columns to brandish in the CSV file.
The matter I need to practice to achieve that is to:
- Replace the previous download link with a dropdown that contains a form
- Replace the hardcoded fields listing in export controller and use dynamic input
Replace the download link
Add a live dropdown component
For the dropdown functionality, I will add a dropdown component. In theory, the dropdown component can be reused simply for now, I will merely apply it once.
# lib/tutorial_web/live/shared_components.ex defmodule TutorialWeb.Live.SharedComponents practice use Phoenix.Component alias Phoenix.LiveView.JS def dropdown(assigns) do ~H""" <div form="relative inline-block text-left"> <button class="btn btn-secondary" phx-click={ JS.toggle(to: "##{@id}", in: {"duration-300", "opacity-0", "opacity-100"}, out: {"elapsing-75", "opacity-100", "opacity-0"}) } > <%= render_slot(@toggle) %> </push> <div id={@id} class="accented right-0 z-twenty hidden" phx-click-away={JS.hide(to: "##{@id}", transition: {"duration-75", "opacity-100", "opacity-0"})}> <%= render_slot(@inner_block, assigns) %> </div> </div> """ stop end
Import the new component in the LiveView file.
# lib/tutorial_web/live/customer_live/index.ex import TutorialWeb.Live.SharedComponents
When I accept imported the components file, I can implement information technology in the view. I want the dropdown to brandish a form that posts to the export controller. And the fields in the form should be checkboxes for each possible column.
<!-- lib/tutorial_web/live/customer_live/index.html.heex --> <div grade="flex justify-end mb-2"> <.dropdown> <:toggle> Export <svg xmlns="http://www.w3.org/2000/svg" fill up="currentColor" course="westward-vi h-6 ml-1" viewBox="0 0 xvi 16"> <path fill-rule="evenodd" d="M14 4.5V14a2 two 0 0 1-2 2h-1v-1h1a1 one 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 i 9.v 3V1H4a1 1 0 0 0-1 1v9H2V2a2 ii 0 0 1 2-2h5.5L14 iv.5ZM3.517 14.841a1.thirteen 1.13 0 0 0 .401.823c.13.108.289.192.478.252.xix.061.411.091.665.091.338 0 .624-.053.859-.158.236-.105.416-.252.539-.44.125-.189.187-.408.187-.656 0-.224-.045-.41-.134-.56a1.001 one.001 0 0 0-.375-.357 2.027 2.027 0 0 0-.566-.21l-.621-.144a.97.97 0 0 1-.404-.176.37.37 0 0 1-.144-.299c0-.156.062-.284.185-.384.125-.101.296-.152.512-.152.143 0 .266.023.37.068a.624.624 0 0 1 .246.181.56.56 0 0 1 .12.258h.75a1.092 1.092 0 0 0-.2-.566 1.21 1.21 0 0 0-.5-.41 1.813 1.813 0 0 0-.78-.152c-.293 0-.551.05-.776.xv-.225.099-.4.24-.527.421-.127.182-.19.395-.xix.639 0 .201.04.376.122.524.082.149.two.27.352.367.152.095.332.167.539.213l.618.144c.207.049.361.113.463.193a.387.387 0 0 1 .152.326.505.505 0 0 1-.085.29.559.559 0 0 ane-.255.193c-.111.047-.249.07-.413.07-.117 0-.223-.013-.32-.04a.838.838 0 0 one-.248-.115.578.578 0 0 1-.255-.384h-.765ZM.806 thirteen.693c0-.248.034-.46.102-.633a.868.868 0 0 1 .302-.399.814.814 0 0 ane .475-.137c.15 0 .283.032.398.097a.7.7 0 0 1 .272.26.85.85 0 0 1 .12.381h.765v-.072a1.33 1.33 0 0 0-.466-.964 1.441 i.441 0 0 0-.489-.272 1.838 1.838 0 0 0-.606-.097c-.356 0-.66.074-.911.223-.25.148-.44.359-.572.632-.thirteen.274-.196.vi-.196.979v.498c0 .379.064.704.193.976.131.271.322.48.572.626.25.145.554.217.914.217.293 0 .554-.055.785-.164.23-.11.414-.26.55-.454a1.27 one.27 0 0 0 .226-.674v-.076h-.764a.799.799 0 0 1-.118.363.7.7 0 0 i-.272.25.874.874 0 0 one-.401.087.845.845 0 0 i-.478-.132.833.833 0 0 one-.299-.392 1.699 1.699 0 0 1-.102-.627v-.495Zm8.239 2.238h-.953l-i.338-3.999h.917l.896 3.138h.038l.888-3.138h.879l-one.327 4Z"/> </svg> </:toggle> <ul class="mt-one edge shadow-xl menu bg-base-100 border-base of operations-300 rounded-box w-52"> <li class="p-four"> <div form="mb-2"> <.form allow={f} for={:export_field} action={Routes.export_path(@socket, :create)}> <%= for attr <- ~westward(proper noun address zip metropolis longitude latitude)a practise %> <label class="justify-showtime cursor-pointer label"> <%= checkbox f, attr, checked: truthful, class: "checkbox-sm" %> <%= label f, attr, class: "ml-2 label-text" %> </label> <% end %> <%= submit "Export", class: "btn btn-secondary due west-total" %> </.class> </div> </li> </ul> </.dropdown> </div>
Supersede the hardcoded fields
I have already made some preparations for this change in the controller. The merely affair left to do here is to make the controller take dynamic inputs from the form.
Instead of the hardcoded fields:
fields = [:name, :address, :naught, :city, :phone, :longitude, :breadth]
I need to change to:
def create(conn, %{"export_field" => export_fields} = _params) do fields = Enum.reduce(export_fields, [], fn {field, agile}, acc -> field = String.to_existing_atom(field) if active == "truthful", practise: [field|acc], else: acc end)
Note Each field that I mail service are either "truthful"
or "faux"
. I just want to use the truthy ones.
If I test this now, I should be able to choice the columns I desire and and then export the customers to a CSV file.
montanezdient1993.blogspot.com
Source: https://fullstackphoenix.com/tutorials/csv-export
0 Response to "How to Read a Csv File in Phoenix"
Post a Comment