LiveView Uploadを実装してみる

# 参考資料

https://hexdocs.pm/phoenix_live_view/uploads.html

- allow_uploadでアップロードできるファイルを指定
- live_file_inputタグはファイルの受付画面を作る
-  handle_event("validate", _params, socket)で検証
-  handle_event("save", _params, socket)で保存

# プロジェクトの準備

```
$ phx.new upload_experiment --no-ecto
```

# ソース追加
```elixir:lib/upload_experiment_web/live/upload_live.ex
defmodule UploadExperimentWeb.UploadLive do
  use UploadExperimentWeb, :live_view

  @impl Phoenix.LiveView
  def mount(_params, _session, socket) do
    {:ok,
    socket
    |> assign(:uploaded_files, [])
    |> allow_upload(:avatar, accept: ~w(.png), max_entries: 2)}
  end

  @impl Phoenix.LiveView
  def handle_event("validate", _params, socket) do
    {:noreply, socket}
  end

  @impl Phoenix.LiveView
  def handle_event("cancel-upload", %{"ref" => ref}, socket) do
    {:noreply, cancel_upload(socket, :avatar, ref)}
  end

  @impl Phoenix.LiveView
  def handle_event("save", _params, socket) do
    uploaded_files =
      consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry ->
        dest = Path.join("priv/static/images/uploads", Path.basename(path))
        # You will need to create `priv/static/uploads` for `File.cp!/2` to work.
        File.cp!(path, dest)
        {:ok, ~p"/images/uploads/#{Path.basename(dest)}"}
      end)

    {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}
  end

  defp error_to_string(:too_large), do: "Too large"
  defp error_to_string(:too_many_files), do: "You have selected too many files"
  defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
end
```

```elixir:lib/upload_experiment_web/live/upload_live.html.heex
<form id="upload-form" phx-submit="save" phx-change="validate">
    <.live_file_input upload={@uploads.avatar} />
    <button type="submit">Upload</button>
</form>

<section phx-drop-target={@uploads.avatar.ref}>
<%!-- render each avatar entry --%>
<%= for entry <- @uploads.avatar.entries do %>
  <article class="upload-entry">

    <figure>
      <.live_img_preview entry={entry} />
      <figcaption><%= entry.client_name %></figcaption>
    </figure>

    <%!-- entry.progress will update automatically for in-flight entries --%>
    <progress value={entry.progress} max="100"> <%= entry.progress %>% </progress>

    <%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%>
    <button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref} aria-label="cancel">&times;</button>

    <%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%>
    <%= for err <- upload_errors(@uploads.avatar, entry) do %>
      <p class="alert alert-danger"><%= error_to_string(err) %></p>
    <% end %>

  </article>
<% end %>

<%!-- Phoenix.Component.upload_errors/1 returns a list of error atoms --%>
<%= for err <- upload_errors(@uploads.avatar) do %>
  <p class="alert alert-danger"><%= error_to_string(err) %></p>
<% end %>

</section>
```

```diff_elixir:lib/upload_experiment_web/router.ex
defmodule UploadExperimentWeb.Router do
  # 〜省略〜
  scope "/", UploadExperimentWeb do
    pipe_through :browser

+   live "/up", UploadLive, :index

    get "/", PageController, :home
  end
  # 〜省略〜
end
```

```:下記のフォルダーを作成する
priv/static/images/uploads
```

# 実行

```:下記にアクセス
http://localhost:4000/up
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/b0d24bea-c1b6-522f-b690-191108108537.png)

ファイル選択後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/5e8d03b8-45c4-ba38-5f32-0ea264e18cb7.png)

 「Upload」クリック後のファイルのアップロードを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295814/4b4639e3-4a8c-bdbc-24f5-5aa6a14c5d24.png)

# ソース(github)
https://github.com/ymn-t-yamanashi/upload_experiment