brainbolt

brainbolt

I’m pretty new to Phoenix, so bear with me here :folded_hands:

I’m integrating the Trix editor (GitHub - basecamp/trix: A rich text editor for everyday writing · GitHub) into a LiveView. I’ve got the text-editing features working, but now I’m working on file uploads, and I’m wondering if it is possible to use live uploads to make this work.

File uploads in Trix are accomplished by listening for a javascript event emitted by the editor on the client:

Storing Attached Files

Trix automatically accepts files dragged or pasted into an editor and inserts them as attachments in the document. Each attachment is considered pending until you store it remotely and provide Trix with a permanent URL.

To store attachments, listen for the trix-attachment-add event. Upload the attached files with XMLHttpRequest yourself and set the attachment’s URL attribute upon completion. See the attachment example for detailed information.

Is it possible to initiate a live upload from client-side javascript like this? If so, can anyone point me in the right direction on how to get started?

Much appreciated :folded_hands:

Showing Posts 1 to 4

chrismccord

chrismccord

Creator of Phoenix

phoenix hooks expose a this.upload/uploadTo function to do exactly that JavaScript interoperability — Phoenix LiveView v1.2.5

  • upload(name, files) - method to inject a list of file-like objects into an uploader.
  • uploadTo(selectorOrTarget, name, files) - method to inject a list of file-like objects into an uploader.
    The hook will send the files to the uploader with name defined by allow_upload/3
    on the server-side. Dispatching new uploads triggers an input change event which will be sent to the
    LiveComponent or LiveView the selectorOrTarget is defined in, where its value can be either a query selector or an
    actual DOM element. If the query selector returns more than one live file input, an error will be logged.
brainbolt

brainbolt OP

Oh bummer, I must have missed that when I looked through the docs. Thanks!

tomasz-tomczyk

tomasz-tomczyk

Apologies for resurrecting an old thread, I’m just trying to use Trix + Phoenix LiveView myself. Did you get to a working solution with upload()?

I’ve got as far as this:

Custom input component that has the phx-hook="Trix" and contains a hidden live_file_input component to capture files

  def input(%{type: "trix"} = assigns) do
    ~H"""
    <div phx-feedback-for={@name} phx-hook="Trix" phx-debounce="blur" id={"#{@id}-container"}>
      <.label for={@id}><%= @label %></.label>
      <input
        type="hidden"
        name={@name}
        id={@id}
        value={Phoenix.HTML.Form.normalize_value(@type, @value)}
        {@rest}
      />
      <.live_file_input upload={@upload} class="hidden" />
      <div id="trix-editor-container" phx-update="ignore">
        <trix-editor class="trix-editor" input={@id}></trix-editor>
      </div>
      <.error :for={msg <- @errors}><%= msg %></.error>
    </div>
    """
  end

In JS, callback for uploading files (omitted some of Trix event listeners:

uploadFileAttachment(attachment) {
    const { file } = attachment;
    const input = document.querySelector("input[name='attachment']");

    const dataTransfer = new DataTransfer();
    dataTransfer.items.add(file);
    input.files = dataTransfer.files;

    // Trigger the LiveView file upload
    const upload = this.upload("attachment", [file]);
    attachment.setAttributes({ url: "https://via.placeholder.com/150" });
  },

This correctly sends data to the component and I get it all here:

  def mount(socket) do
    {:ok,
     socket
     |> assign(:uploaded_files, [])
     |> allow_upload(:attachment,
       accept: ~w(.jpg .jpeg .png),
       progress: &handle_progress/3,
       max_entries: 10000,
       auto_upload: true
     )}
  end

  defp handle_progress(:attachment, entry, socket) do
    if entry.done? do
      uploaded_file =
        consume_uploaded_entry(socket, entry, fn %{path: path} = meta ->
          dest = Path.join("priv/static/uploads", Path.basename(path))
          File.cp!(path, dest)
          # Todo: figure out public URL
          {:ok, dest}
        end)

      socket = assign(socket, uploaded_files: [uploaded_file | socket.assigns.uploaded_files])

      {:noreply, put_flash(socket, :info, "file uploaded")}
    else
      {:noreply, socket}
    end
  end

Ignoring the fact that dest isn’t an actual URL to the file, even if I generate one, I don’t think I can pass it back to the JS – attachment.setAttributes({ url: "https://via.placeholder.com/150" }); expects a “saved” URL that’s permanent.

I’m now leaning towards a standalone controller to handle uploads as described in here How to handle file upload using Trix editor in a Phoenix application but I wondered if I’m missing something.

f0rest8

f0rest8

What do you mean by “I don’t think I can pass it back to the JS”? You could have your handle_progress/3 return a :reply instead of :noreply that contains the url you want? You could also push_event/3 and listen for that event on the client side — I’m not sure I understand what issue you’re having.

I used another method to do similar to the example provided by the trix documentation and the link you included at the bottom of your post and use a controller that then implements the uploader and upload logic.

# in MyAppWeb.TrixUploadsController
  def create(conn, params) do
    case impl().upload(conn.private.plug_session, params) do
      {:ok, file_url} -> send_resp(conn, 201, file_url)
      {:error, _reason} -> send_resp(conn, 400, "Unable to upload file, please try again later.")
    end
  end

  defp impl, do: Application.get_env(:my_app, :uploader)[:adapter]

Similary, removing attachments in the trix-editor calls delete/2:

# in MyAppWeb.TrixUploadsController
  def delete(conn, %{"key" => key, "content_type" => content_type}) do
    case impl().delete_file(key, content_type) do
      :ok -> send_resp(conn, 204, "File successfully deleted")
      {:error, _reason} -> send_resp(conn, 400, "Unable to delete file, please try again later.")
    end
  end

In the uploader you can implement processing the file as well as sending the request. In our case for Mosslet, we check the image for safety, presign urls, and encrypt the image before sending it to cloud storage.

This works well and then I implement more checks in the trix.js to communicate with the server to handle undo/redo of attachments and ensuring the form isn’t submitted before the attachments are uploaded to the cloud and urls are ready to be saved to the database.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews