Загрузка файлов
SourceLiveView поддерживает интерактивную загрузку файлов с отображением прогресса как непосредственно на сервер, так и напрямую в облако с помощью внешней загрузки на клиенте.
Встроенные функции
Спецификация допустимых файлов — задайте допустимые типы файлов, максимальное число файлов, максимальный размер файла и т. д. Когда клиент выбирает файл или файлы, метаданные файлов автоматически проверяются по спецификации. См.
Phoenix.LiveView.allow_upload/3.Реактивные записи — загрузки хранятся в assign
@uploadsсокета. Записи автоматически реагируют на изменение прогресса, ошибки, отмену и т. д.Перетаскивание — используйте атрибут
phx-drop-targetдля включения этой возможности. См.Phoenix.Component.live_file_input/1.
Разрешение загрузки файлов
Разрешить загрузку файлов можно, как правило, в mount, с помощью allow_upload/3.
В этом примере мы также сохраним список загруженных файлов в новом assign с именем uploaded_files, но вы можете выбрать любое другое имя.
@impl Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:avatar, accept: ~w(.jpg .jpeg), max_entries: 2)}
end
На этом пока всё! Позже мы вернёмся к LiveView, чтобы реализовать некоторые обратные вызовы, связанные с формой и загрузкой файлов, но большая часть функциональности загрузки реализуется в шаблоне.
Стоит ознакомиться с документацией по allow_upload/3 и изучить доступные параметры, среди которых есть, например, auto_upload для автоматической загрузки файлов.
Отображение реактивных элементов
Используйте компонент Phoenix.Component.live_file_input/1, чтобы отобразить поле выбора файлов для загрузки:
<%!-- lib/my_app_web/live/upload_live.html.heex --%>
<form id="upload-form" phx-change="validate" phx-submit="save">
<.live_file_input upload={@uploads.avatar} />
<button type="submit">Upload</button>
</form>
Важно: Для формы необходимо задать
phx-submitиphx-change.
Обратите внимание: хотя live_file_input/1 позволяет задавать дополнительные атрибуты поля выбора файла, многие атрибуты, такие как id, accept и multiple, будут заданы автоматически на основе спецификации allow_upload/3.
Шаблон будет реактивно обновляться по мере взаимодействия конечного пользователя с полем выбора файла.
Записи загрузки
Загрузки хранятся в assign @uploads сокета. Каждая разрешённая загрузка содержит список записей независимо от значения :max_entries в спецификации allow_upload/3. Эти структуры записей содержат всю информацию о загрузке, включая прогресс, сведения о файле на клиенте, ошибки и т. д.
Рассмотрим пример с пояснениями:
<%!-- lib/my_app_web/live/upload_live.html.heex --%>
<%!-- use phx-drop-target with the upload ref to enable file drag and drop --%>
<section phx-drop-target={@uploads.avatar.ref}>
<%!-- render each avatar entry --%>
<article :for={entry <- @uploads.avatar.entries} 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">×</button>
<%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%>
<p :for={err <- upload_errors(@uploads.avatar, entry)} class="alert alert-danger">{error_to_string(err)}</p>
</article>
<%!-- Phoenix.Component.upload_errors/1 returns a list of error atoms --%>
<p :for={err <- upload_errors(@uploads.avatar)} class="alert alert-danger">
{error_to_string(err)}
</p>
</section>
Элемент section в примере служит phx-drop-target для загрузки :avatar. Пользователи могут взаимодействовать с полем выбора файла или перетаскивать файлы на этот элемент, чтобы добавить новые записи.
Записи загрузки создаются при добавлении файла в поле формы, и каждая из них существует до тех пор, пока файл не будет обработан после успешного завершения загрузки.
Стилизация области для перетаскивания
Phoenix LiveView отслеживает события перетаскивания в браузере и добавляет к элементу-цели перетаскивания класс phx-drop-target-active, когда пользователь перетаскивает на него элемент.
При использовании TailwindCSS можно создать пользовательский вариант, который будет работать вместе с этим классом и позволит применять стили именно во время перетаскивания элемента на цель.
<%!-- assets/app.css --%> @custom-variant phx-drop-target-active (.phx-drop-target-active&, .phx-drop-target-active &);
Этот вариант можно использовать в шаблонах HeEx следующим образом:
<%!-- lib/my_app_web/live/upload_live.html.heex --%>
<section phx-drop-target={@uploads.avatar.ref} class="phx-drop-target-active:scale-105">
<!-- ... -->
</section>
В этом примере элемент увеличивается, когда файл перетаскивают на область для перетаскивания.
Этот вариант также можно использовать вместе с произвольными селекторами состояний Tailwind. Это позволяет стилизовать не только сам элемент, но и всю страницу, соседние элементы, родительские элементы и многое другое.
Проверка записей
Проверка выполняется автоматически на основе условий, заданных в allow_upload/3. Однако, как упоминалось ранее, для выполнения проверки необходимо задать phx-change для формы. Поэтому нужно реализовать как минимум минимальный обратный вызов:
@impl Phoenix.LiveView
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
Записи файлов, не соответствующих спецификации allow_upload/3, будут содержать ошибки. Используйте Phoenix.Component.upload_errors/2 и собственную вспомогательную функцию, чтобы вывести понятное сообщение об ошибке:
defp error_to_string(:too_large), do: "Too large" defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
Для сообщений об ошибках, относящихся ко всем записям, используйте Phoenix.Component.upload_errors/1 и собственную вспомогательную функцию, чтобы вывести понятное сообщение об ошибке:
defp error_to_string(:too_many_files), do: "You have selected too many files"
Отмена записи
Записи загрузки можно отменять программно или в результате действий пользователя. Например, чтобы обработать событие щелчка в приведённом выше шаблоне, можно сделать следующее:
@impl Phoenix.LiveView
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :avatar, ref)}
end
Обработка загруженных записей
Когда конечный пользователь отправляет форму с live_file_input/1, клиент JavaScript сначала загружает файл или файлы, а затем вызывает обратный вызов для события phx-submit формы.
В обратном вызове для события phx-submit вызовите функцию Phoenix.LiveView.consume_uploaded_entries/3, чтобы обработать завершённые загрузки и сохранить необходимые данные о загруженных файлах вместе с данными формы:
@impl Phoenix.LiveView
def handle_event("save", _params, socket) do
uploaded_files =
consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry ->
dest = Path.join(Application.app_dir(:my_app, "priv/static/uploads"), Path.basename(path))
# You will need to create `priv/static/uploads` for `File.cp!/2` to work.
File.cp!(path, dest)
{:ok, ~p"/uploads/#{Path.basename(dest)}"}
end)
{:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}
end
Примечание: Доверять метаданным клиента нельзя, однако при загрузке непосредственно на сервер максимальный размер файла проверяется по мере получения каждого фрагмента.
В этом примере файл записывается непосредственно на диск в папку priv. Чтобы получить доступ к загруженному файлу, например в теге <img />, необходимо добавить каталог uploads в static_paths/0. В стандартном проекте Phoenix это находится в lib/my_app_web.ex.
Также следует учитывать, что в режиме разработки изменения в priv/static/uploads будут обнаружены компонентом live_reload. Это означает, что сразу после успешной загрузки приложение будет перезагружено в браузере. Это поведение можно временно отключить, задав code_reloader: false в config/dev.exs.
Кроме того, у этого подхода есть ограничения в рабочей среде. Если приложение запущено в нескольких экземплярах, загруженный файл будет сохранён только в одном из них. Любой запрос, направленный на другой сервер, в итоге завершится ошибкой.
По этим причинам лучше хранить загруженные файлы в другом месте, например в базе данных (в зависимости от размера и содержимого) или в отдельном сервисе хранения. Подробнее о реализации клиентской загрузки напрямую в облако см. в руководстве по внешней загрузке.
Приложение A: UploadLive
Полный пример LiveView из этого руководства:
# lib/my_app_web/live/upload_live.ex
defmodule MyAppWeb.UploadLive do
use MyAppWeb, :live_view
@impl Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:avatar, accept: ~w(.jpg .jpeg), 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([:code.priv_dir(:my_app), "static", "uploads", Path.basename(path)])
# You will need to create `priv/static/uploads` for `File.cp!/2` to work.
File.cp!(path, dest)
{:ok, ~p"/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
Чтобы получить доступ к загруженным файлам через приложение, обязательно добавьте uploads в MyAppWeb.static_paths/0.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix-live-view.hexdocs.pm/uploads.html