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

  1. Add together CSV library to mix file
  2. Create a controller for the export
  3. Add export CSV route to the routes file
  4. 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:

  1. Replace the previous download link with a dropdown that contains a form
  2. 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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel