Why doesn't the file_input in the form on a Phoenix LiveView return a %Plug.Upload{}?

爱⌒轻易说出口 提交于 2020-12-30 07:58:25

问题


I have a form in a Phoenix LiveView that contains a file_input. I want to use it to allow a user to upload an image. I'm having trouble understanding what the form is sending to my backend, and what I can do with it. I expected a %Plug.Upload{} representation of the image file, as described in documentation, but instead I just get "[object File]".

Note that I am not backing the form with a changeset, because I am not using Ecto:

<%= f = form_for :post, "#", phx_submit: :create_post, phx_change: :image_attach, multipart: true %>
  <%= hidden_input f, :user_id, value: @current_user.account_id %>
  <%= textarea f, :message, class: "social-post-box", placeholder: "Something on your mind?" %>
  <div class="post-submit-container">
    <%= submit "Post", class: "post-submit" %>
    <label for="post_image" class="post-submit-image"></label
    <%= file_input f, :image %      
  </div>
</form>

I have a handler in the LiveView module to handle the submitted form, and when I inspect the image upload I see "[object File]"

def handle_event("create_post", %{"post" => post_params}, socket) do
  IO.inspect post_params["image"]
  {:noreply, socket}
end

I tried prying at this location so that I could run i post_params["image"], and it explains that the object is a bitstring, ie just a binary. So it's literally just the text "[object File]", and not even a file at all?

What is it that I am receiving from my form? Why isn't it a %Plug.Upload{}? How can I achieve my goal of saving this image upload to the local filesystem?


回答1:


As @sbacaro pointed out, file uploads are not yet supported in LiveView forms.

More info on this:

  • https://github.com/phoenixframework/phoenix_live_view/issues/104
  • https://elixirforum.com/t/elixirconf-2019-how-liveview-handles-file-uploads-gary-rennie/25092

I implemented a Javascript workaround to manually send the form without refreshing the page (so that other parts of the LiveView an continue to function normally).

But were also issues with the way Phoenix handled CSRF tokens in LiveViews. It turns out the LiveView creates a new token when the socket connects from the client, and this token won't be recognized by controllers listening to POSTs from the form. To workaround this you need to manually pass the token into the LiveView.

Overall, this workaround works fine, but I hope that someday in the future someone will point out here that file uploads have achieved support in LiveViews and share an easier way.

My form now looks like this. Note the manual specification of the csrf token:

<%= f = form_for :post, Routes.profile_path(UdsWeb.Endpoint, :post_social, @current_user.username), [phx_change: :image_attach, multipart: true, id: "social-timeline-form", csrf_token: @csrf_token] %>
  <%= hidden_input f, :user_id, value: @current_user.account_id %>
  <%= textarea f, :message, class: "social-post-box", placeholder: "Something on your mind?" %>
  <div class="post-submit-container">
    <%= submit "Post", class: "post-submit" %>
    <label for="post_image" class="post-submit-image"></label>
    <%= file_input f, :image %>
  </div>
</form>

I render the LiveView from within a normal eex template. Note that I'm manually specifying the csrf token here:

<%= Phoenix.LiveView.live_render(@conn, UdsWeb.ProfileTimelineLive, session: %{current_user: @current_user, csrf_token: Phoenix.Controller.get_csrf_token()}, container: {:div, class: "feed"}) %>

The timeline module has a mount function that loads the csrf token into socket assigns:

def mount(%{current_user: current_user, csrf_token: csrf_token}, socket) do
  {:ok, assign(socket, current_user: current_user, csrf_token: csrf_token)}
end

The JS for manually taking control of the form submission isn't really special but here it is:

function handleSocialTimelinePost(e) {
  e.preventDefault();
  let form = document.querySelector("#social-timeline-form");
  let formData = new FormData(form);
  let username = formData.get("post[username]");
  let request = new XMLHttpRequest();
  request.open("POST", `/profile/${username}`);
  request.send(formData);
}

document.querySelector("#social-timeline-form button.post-submit").onclick = handleSocialTimelinePost;


来源:https://stackoverflow.com/questions/59759281/why-doesnt-the-file-input-in-the-form-on-a-phoenix-liveview-return-a-plug-uplo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!