diff --git a/lib/talon/controller.ex b/lib/talon/controller.ex index c278c08..e466ae7 100644 --- a/lib/talon/controller.ex +++ b/lib/talon/controller.ex @@ -86,7 +86,16 @@ defmodule Talon.Controller do # TODO: rename to ResourceController (DJS) |> render("search.html", conn: conn) end - defoverridable [index: 2, show: 2, new: 2, edit: 2, create: 2, update: 2, delete: 2, set_repo: 2, search: 2] + def csv(conn, _params) do + repo = Talon.View.repo(conn) + resources = repo.all(conn.assigns.talon.schema) + csv_schema = Talon.CSV.normalize_schema(conn.assigns.talon.talon_resource.csv_schema()) + file_path = Talon.CSV.write_file(resources, csv_schema) + file_name = conn.assigns.talon.talon_resource.index_card_title() <> ".csv" + send_download(conn, {:file, file_path}, filename: file_name) + end + + defoverridable [index: 2, show: 2, new: 2, edit: 2, create: 2, update: 2, delete: 2, set_repo: 2, search: 2, csv: 2] end end diff --git a/lib/talon/csv.ex b/lib/talon/csv.ex new file mode 100644 index 0000000..a777200 --- /dev/null +++ b/lib/talon/csv.ex @@ -0,0 +1,45 @@ +defmodule Talon.CSV do + @moduledoc """ + """ + + def write_file(resources, schema) do + random_name = + :crypto.strong_rand_bytes(16) + |> Base.url_encode64 + |> binary_part(0, 16) + file_path = System.tmp_dir() <> random_name + file = File.open!(file_path, [:write, :utf8]) + rows = Enum.map(resources, fn(resource) -> + build_row(resource, schema) + end) + + [build_header(schema) | rows] + |> CSV.encode + |> Enum.each(&IO.write(file, &1)) + + :ok = File.close(file) + file_path + end + + def normalize_schema(schema) do + Enum.map schema, fn + {name, fun} -> %{field: name, fun: fun} + name when is_atom(name) -> %{field: name, fun: nil} + map -> map + end + end + + defp build_header(schema) do + for field <- schema, do: field[:field] + end + + defp build_row(resource, schema) do + Enum.reduce(schema, [], fn + %{field: name, fun: nil}, acc -> + [Map.get(resource, name) | acc] + %{field: _name, fun: fun}, acc -> + [fun.(resource) | acc] + end) + |> Enum.reverse + end +end diff --git a/lib/talon/resource.ex b/lib/talon/resource.ex index 25a3997..38ba5f0 100644 --- a/lib/talon/resource.ex +++ b/lib/talon/resource.ex @@ -71,6 +71,10 @@ defmodule Talon.Resource do @__module__.__schema__(:fields) -- ~w(id inserted_at updated_at)a end + def csv_schema do + @__module__.__schema__(:fields) -- ~w(inserted_at updated_at)a + end + @doc """ Translates column atoms into human title format. diff --git a/mix.exs b/mix.exs index f3f70ec..038fdb7 100644 --- a/mix.exs +++ b/mix.exs @@ -37,6 +37,7 @@ defmodule Talon.Mixfile do {:ecto_talon, github: "talonframework/ecto_talon"}, {:mix_test_watch, "~> 0.3", only: :dev, runtime: false}, {:gettext, "~> 0.11", only: :test}, + {:csv, "~> 2.0"}, {:dialyxir, "~> 0.5.0", only: [:dev]} # {:ecto_talon, path: "../ecto_talon", only: :test}, ] diff --git a/priv/templates/talon.gen.components/components/datatable/templates/datatable.html.slim b/priv/templates/talon.gen.components/components/datatable/templates/datatable.html.slim index c22de0c..8285d9e 100644 --- a/priv/templates/talon.gen.components/components/datatable/templates/datatable.html.slim +++ b/priv/templates/talon.gen.components/components/datatable/templates/datatable.html.slim @@ -32,3 +32,5 @@ .box-footer.clear-fix = <%= base %>.<%= concern %>.<%= theme_module %>.<%= web_namespace %>PaginateView.paginate(@conn) + + a href="#{talon_resource.route_name}/csv" Download CSV