为什么 Phoenix LiveView return 表单中的 file_input 不是 %Plug.Upload{}?
Why doesn't the file_input in the form on a Phoenix LiveView return a %Plug.Upload{}?
我在 Phoenix LiveView 中有一个表单,其中包含 file_input
。我想用它来允许用户上传图像。我无法理解表单发送到我的后端的内容,以及我可以用它做什么。我期望图像文件的 %Plug.Upload{} 表示,如 documentation 中所述,但我只是得到 "[object File]"
。
请注意,我没有使用变更集支持表单,因为我没有使用 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>
我在 LiveView 模块中有一个处理程序来处理提交的表单,当我检查图像上传时,我看到 "[object File]"
def handle_event("create_post", %{"post" => post_params}, socket) do
IO.inspect post_params["image"]
{:noreply, socket}
end
我尝试在这个位置窥探,以便我可以 运行 i post_params["image"]
,它解释说该对象是一个位串,即只是一个二进制文件。所以它实际上只是文本“[object File]”,甚至根本不是文件?
我从我的表格中收到了什么?为什么不是 %Plug.Upload{}
?如何实现将上传的图像保存到本地文件系统的目标?
正如@sbacaro 所指出的,LiveView 表单尚不支持文件上传。
关于此的更多信息:
- https://github.com/phoenixframework/phoenix_live_view/issues/104
- https://elixirforum.com/t/elixirconf-2019-how-liveview-handles-file-uploads-gary-rennie/25092
我实施了一个 Javascript 解决方法来手动发送表单而不刷新页面(以便 LiveView 的其他部分继续正常运行)。
但 Phoenix 在 LiveViews 中处理 CSRF 令牌的方式也存在问题。事实证明,当套接字从客户端连接时,LiveView 创建了一个新的令牌,并且这个令牌不会被从表单中侦听 POST 的控制器识别。要解决此问题,您需要手动将令牌传递到 LiveView。
总的来说,这个解决方法工作正常,但我希望将来有一天有人会在这里指出文件上传已在 LiveViews 中获得支持并分享更简单的方法。
我的表单现在看起来像这样。请注意 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>
我从普通的 eex 模板中渲染 LiveView。请注意,我在这里手动指定 csrf 令牌:
<%= Phoenix.LiveView.live_render(@conn, UdsWeb.ProfileTimelineLive, session: %{current_user: @current_user, csrf_token: Phoenix.Controller.get_csrf_token()}, container: {:div, class: "feed"}) %>
timeline模块有一个mount函数,可以将csrf token加载到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
用于手动控制表单提交的 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;
我在 Phoenix LiveView 中有一个表单,其中包含 file_input
。我想用它来允许用户上传图像。我无法理解表单发送到我的后端的内容,以及我可以用它做什么。我期望图像文件的 %Plug.Upload{} 表示,如 documentation 中所述,但我只是得到 "[object File]"
。
请注意,我没有使用变更集支持表单,因为我没有使用 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>
我在 LiveView 模块中有一个处理程序来处理提交的表单,当我检查图像上传时,我看到 "[object File]"
def handle_event("create_post", %{"post" => post_params}, socket) do
IO.inspect post_params["image"]
{:noreply, socket}
end
我尝试在这个位置窥探,以便我可以 运行 i post_params["image"]
,它解释说该对象是一个位串,即只是一个二进制文件。所以它实际上只是文本“[object File]”,甚至根本不是文件?
我从我的表格中收到了什么?为什么不是 %Plug.Upload{}
?如何实现将上传的图像保存到本地文件系统的目标?
正如@sbacaro 所指出的,LiveView 表单尚不支持文件上传。
关于此的更多信息:
- https://github.com/phoenixframework/phoenix_live_view/issues/104
- https://elixirforum.com/t/elixirconf-2019-how-liveview-handles-file-uploads-gary-rennie/25092
我实施了一个 Javascript 解决方法来手动发送表单而不刷新页面(以便 LiveView 的其他部分继续正常运行)。
但 Phoenix 在 LiveViews 中处理 CSRF 令牌的方式也存在问题。事实证明,当套接字从客户端连接时,LiveView 创建了一个新的令牌,并且这个令牌不会被从表单中侦听 POST 的控制器识别。要解决此问题,您需要手动将令牌传递到 LiveView。
总的来说,这个解决方法工作正常,但我希望将来有一天有人会在这里指出文件上传已在 LiveViews 中获得支持并分享更简单的方法。
我的表单现在看起来像这样。请注意 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>
我从普通的 eex 模板中渲染 LiveView。请注意,我在这里手动指定 csrf 令牌:
<%= Phoenix.LiveView.live_render(@conn, UdsWeb.ProfileTimelineLive, session: %{current_user: @current_user, csrf_token: Phoenix.Controller.get_csrf_token()}, container: {:div, class: "feed"}) %>
timeline模块有一个mount函数,可以将csrf token加载到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
用于手动控制表单提交的 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;