In my personal project — an RSS reader — I wanted to add a feature that lets users upload an OPML file containing their RSS subscriptions. (The OPML parsing itself is covered in Counting Successful Records in Elixir with Enum.)
Today, I’ll walk through how I added the file upload functionality using Phoenix LiveView.
Setting up the upload in mount/3
First, I updated the mount/3
callback to allow file uploads:
defmodule RssReaderWeb.FolderLive.Show do
use RssReaderWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> allow_upload(:file,
accept: :any,
max_entries: 1,
max_file_size: 10_000_000,
auto_upload: true,
progress: &handle_progress/3
)}
end
end
This configuration allows a single file up to 10 MB to be uploaded automatically, and sets up a handle_progress/3
callback to process the upload.
Handling upload progress
Next, I implemented the handle_progress/3
function, along with a basic handle_event/3
for validation:
defmodule RssReaderWeb.FolderLive.Show do
alias RssReader.Feeds.OpmlImport
@impl true
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
defp handle_progress(:file, entry, socket) do
if entry.done? do
uploaded_files =
consume_uploaded_entries(socket, :file, fn %{path: path}, _entry ->
{:ok, File.read!(path)}
end)
file_content = List.first(uploaded_files)
folder_id = socket.assigns.folder.id
source_count = OpmlImport.import(file_content, folder_id)
{sources, unread_count} = sources_with_unread_count(folder_id)
{:noreply,
socket
|> assign(:sources, sources)
|> assign(:unread_count, unread_count)
|> put_flash(:info, "#{source_count} sources imported!")}
else
{:noreply, socket}
end
end
end
Here’s what’s happening:
- Once the upload is complete (
entry.done?
), the file is consumed and its content is read. - I then call
OpmlImport.import/2
to import the subscriptions into the current folder. - After importing, I refresh the folder’s sources and unread count.
- Finally, I update the LiveView state and show a success flash with the number of sources imported.
Adding the upload button to the template
Finally, I added a simple file upload button in the HEEx template:
<form phx-submit="upload">
<.live_file_input upload={@uploads.file} class="hidden" />
<label
for={@uploads.file.ref}
class="inline-block cursor-pointer rounded bg-white px-2 py-1 text-xs font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100"
>
<.icon name="hero-arrow-up-on-square-stack" size="4" />
</label>
</form>
The <.live_file_input>
is hidden, and the <label>
acts as a styled button. Clicking the label triggers the file selector.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.