简体   繁体   English

为什么 Phoenix LiveView 表单中的 file_input 不返回 %Plug.Upload{}?

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

I have a form in a Phoenix LiveView that contains a file_input .我在 Phoenix LiveView 中有一个表单,其中包含一个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]" .我期望图像文件的 %Plug.Upload{} 表示,如文档中所述,但我只是得到"[object File]"

Note that I am not backing the form with a changeset, because I am not using Ecto:请注意,我没有使用变更集支持表单,因为我没有使用 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]"我在 LiveView 模块中有一个处理程序来处理提交的表单,当我检查图像上传时,我看到"[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.我试着在这个位置撬,这样我就可以运行i post_params["image"] ,它解释了这个对象是一个位串,即只是一个二进制文件。 So it's literally just the text "[object File]", and not even a file at all?所以它实际上只是文本“[object File]”,甚至根本不是文件?

What is it that I am receiving from my form?我从我的表格中收到了什么? Why isn't it a %Plug.Upload{} ?为什么不是%Plug.Upload{} How can I achieve my goal of saving this image upload to the local filesystem?如何实现将上传的图片保存到本地文件系统的目标?

As @sbacaro pointed out, file uploads are not yet supported in LiveView forms.正如@sbacaro 指出的那样,LiveView 表单尚不支持文件上传。

More info on this:更多信息:

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).我实现了一个 Javascript 解决方法来手动发送表单而不刷新页面(以便 LiveView 的其他部分继续正常运行)。

But were also issues with the way Phoenix handled CSRF tokens in LiveViews.但是,Phoenix 在 LiveViews 中处理 CSRF 令牌的方式也存在问题。 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.事实证明,当套接字从客户端连接时,LiveView 会创建一个新令牌,并且该令牌不会被监听表单 POST 的控制器识别。 To workaround this you need to manually pass the token into the LiveView.要解决此问题,您需要手动将令牌传递到 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.总体而言,这种解决方法工作正常,但我希望将来有一天有人会在这里指出文件上传已在 LiveViews 中获得支持并分享一种更简单的方法。

My form now looks like this.我的表格现在看起来像这样。 Note the manual specification of the csrf token:请注意 csrf 令牌的手动规范:

<%= 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.我从一个普通的 eex 模板中渲染 LiveView。 Note that I'm manually specifying the csrf token here:请注意,我在这里手动指定了 csrf 令牌:

<%= 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:时间线模块有一个挂载函数,可以将 csrf 令牌加载到套接字分配中:

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:用于手动控制表单提交的 JS 并不是很特别,但它是:

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM