我有一个带有实时聊天功能的 Rails 应用程序(1v1)。我想通过 AJAX 实现文件上传功能,在提交文件上传表单时,文件名将显示在聊天中,而无需重新加载整个页面。
我正在尝试使用 remotipart gem 来通过 AJAX 提交 refile 文件上传表单。文件直接上传到 S3,所以那些根本不会打到我的应用程序(我不需要显示它们),我只是通过聊天将文件名/URL 发送给其他用户。
由于 refile gem,当我选择文件时,这些上传就开始了。因此,当我提交(“发送文件”按钮)表单时,它只是将文件属性(message.message_attachment/id、url、大小等)和 message_id 保存到数据库中。
在图片上你可以看到我想要实现的。聊天输入文本(message.body)有一个单独的表单,文件上传有另一个表单(message.message_attachment)。到目前为止,聊天与 private_pub gem 完美配合。选择文件也正确发生(包括将文件上传到 S3 和 204 状态响应),但提交表单(通过单击“发送文件”将消息保存到数据库)仅适用于整页重新加载。
我尝试的解决方案:
- 如果我没有将 'authenticity_token: true' 放入表单助手中进行上传,那么我会收到无效的 authencity token 错误。
- 如果我正在使用与聊天正常工作的消息控制器,那么我会在表单中收到缺少模板错误。
- 如果我把respond_to js(有或没有渲染:'create.js.erb')放在那里,我会收到未知格式错误。
对于聊天消息,我使用相同的 create.js.erb,但由于 private_pub gem,我不必使用 respond_to js。我只需要订阅和发布到/使用conversation_path(@path)。
我觉得我非常接近解决方案,因为它适用于整页重新加载。我不知道private_pub解决方案(不需要respond_to js)和remotipart w/refile(需要respond_to js)之间的兼容性是否会导致问题。
消息模型:
create_table "messages", force: :cascade do |t|
t.text "body"
t.integer "conversation_id"
t.integer "user_id"
t.string "message_attachment"
t.string "message_attachment_id"
t.string "message_attachment_filename"
t.integer "message_attachment_size"
t.string "message_attachment_content_type"
......
end
创建.js.erb
<% publish_to @path do %>
var id = "<%= @conversation.id %>";
var chatbox = $(".chatboxcontent");
chatbox.append("<%= j render(partial: @message ) %>");
chatbox.scrollTop(chatbox[0].scrollHeight);
$(".chatboxtextarea").val("");
var filechosen = $('.choosefile').val();
if (filechosen != "") {
$(".refile_form").find("input[type=submit]").hide();
$('.choosefile').val("");
$('#progresspercent').hide();
}
<% end %>
消息/_show.html.erb
<div class="chatboxcontent">
<% if @messages.any? %>
<%= render @messages %>
<% end %>
</div>
<div class="chatboxinput">
<%= form_for([@conversation, @message], :remote => true, :html => {id: "conversation_form_#{@conversation.id}"}) do |f| %>
<%= f.text_area :body, class: "chatboxtextarea", "data-cid" => @conversation.id %>
<% end %>
<%= form_for([@conversation, @message], html: {class: "refile_form"}, remote: true, authenticity_token: true) do |form| %>
<span class="btn btn-success btn-sm btn-file">Choose file
<%= form.attachment_field :message_attachment, direct: true, presigned: true, class: "choosefile" %></span>
<%= form.submit "Send File", class: "btn btn-primary btn-sm btn-submit-refile" %>
<% end %>
</div>
<%= subscribe_to conversation_path(@conversation) %>
在实现文件上传之前用于聊天的消息控制器:
def create
@conversation = Conversation.find(params[:conversation_id])
@message = @conversation.messages.build(message_params)
@message.user_id = current_user.id
@message.save!
@path = conversation_path(@conversation)
end
消息控制器可以正常工作,并重新加载整页以进行上传:
def create
@conversation = Conversation.find(params[:conversation_id])
@message = @conversation.messages.build(message_params)
@message.user_id = current_user.id
@message.save!
@path = conversation_path(@conversation)
if @message.message_attachment_id
respond_to do |format|
format.html { redirect_to :back }
# I've tried w/ and with no format.js as well.
#format.js { render: 'create.js.erb }
end
end
end
更新:
问题已解决:app.js 中的错字//=需要 jquery.remotipart...... - 由于 private_pub gem,不需要 response_to js - authencity_token: true 对于表单助手也没有必要,这要归功于 remotipart gem - remotipart 需要能够通过 AJAX 提交文件表单