Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions app/javascript/controllers/file_input_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Controller } from "@hotwired/stimulus";

/**
* Stimulus controller to enhance the file input UI for multiple files.
*
* This controller:
* - Listens for file selection on an `<input type="file" multiple="multiple">`
* - Displays selected file names in a custom list **when multiple files are selected
* - Defaults to the browser’s built-in display for a single file selection
*
* Expected HTML structure should have a placeholder div for the selected file names:
*
* ```erb
* <div data-controller="file-input">
* <input type="file" multiple data-file-input-target="input">
* <div data-file-input-target="list"></div>
* </div>
* ```
*/
export default class extends Controller {
static targets = ["input", "list"];

connect() {
this.inputTarget.addEventListener("change", () => this.updateFileList());
}

updateFileList() {
const files = this.inputTarget.files;
this.listTarget.innerHTML = ""; // Clear previous list

// If no files or only one file is selected, let the native UI handle it
if (files.length <= 1) {
return;
}

const ul = document.createElement("ul");
ul.classList.add("list-unstyled", "mt-2");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fine way to do it, but thinking to myself here I wish we didn't need the mt-2; seems like it might later be harder to maintain.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried removing the mt-2 and it looks fine. This has already been merged but there's still a Part 3 for this issue, so I can remove the extra margin in that PR.


Array.from(files).forEach((file) => {
const li = document.createElement("li");
li.classList.add("p-1", "rounded", "mb-1");
li.textContent = file.name;
ul.appendChild(li);
});

this.listTarget.appendChild(ul);
}
}
20 changes: 13 additions & 7 deletions app/views/partners/profiles/step/_attached_documents_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
<%= f.fields_for :profile, profile do |pf| %>
<div class="form-group">
<div class="form-group" data-controller="file-input">
<% if profile.documents.attached? %>
<strong>Attached files:</strong>
<ul>
<ul class="list-unstyled">
<% profile.documents.each do |doc| %>
<% if doc.persisted? %>
<li><%= link_to doc.blob['filename'], rails_blob_path(doc), class: "font-weight-bold" %></li>
<%= pf.hidden_field :documents, multiple: true, value: doc.signed_id %>
<li class="attached-document d-flex justify-content-between align-items-center p-2 border rounded mb-2" data-document-id="<%= doc.signed_id %>">
<div class="text-truncate" style="max-width: 75%;">
<%= link_to doc.blob.filename.to_s, rails_blob_path(doc), class: "font-weight-bold" %>
<%= pf.hidden_field :documents, multiple: true, value: doc.signed_id %>
</div>
<%= remove_element_button "Remove", container_selector: "li", class: "btn btn-sm btn-danger" %>
</li>
<% end %>
<% end %>
</ul>
<%= pf.file_field :documents, multiple: true, class: "form-control-file" %>
<% else %>
<%= pf.file_field :documents, multiple: true, class: "form-control-file" %>
<% end %>

<%# Native file input and placeholder for selected file names %>
<%= pf.file_field :documents, multiple: true, class: "form-control-file", data: { file_input_target: "input" } %>
<div data-file-input-target="list" class="mt-2"></div>
</div>
<% end %>
45 changes: 43 additions & 2 deletions spec/system/partners/profile_edit_system_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,11 @@
end

it "preserves previously uploaded documents when adding new attachments" do
# Upload the first document
# Open attached documents section
find("button[data-bs-target='#attached_documents']").click
expect(page).to have_css("#attached_documents.accordion-collapse.collapse.show", visible: true)

# Upload the first document
within "#attached_documents" do
attach_file("partner_profile_documents", Rails.root.join("spec/fixtures/files/document1.md"), make_visible: true)
end
Expand Down Expand Up @@ -137,7 +138,47 @@
end
end

it "persists file upload when there are validation errors" do
it "allows removal of attached documents" do
# Open attached documents section
find("button[data-bs-target='#attached_documents']").click
expect(page).to have_css("#attached_documents.accordion-collapse.collapse.show", visible: true)

# Upload two documents - needs to be done individually because Capybara doesn't have attach_files multiple support
# https://github.com/teamcapybara/capybara/issues/37
within "#attached_documents" do
attach_file("partner_profile_documents", Rails.root.join("spec/fixtures/files/document1.md"), make_visible: true)
end
all("input[type='submit'][value='Save Progress']").last.click
visit edit_partners_profile_path
find("button[data-bs-target='#attached_documents']").click
within "#attached_documents" do
attach_file("partner_profile_documents", Rails.root.join("spec/fixtures/files/document2.md"), make_visible: true)
end
all("input[type='submit'][value='Save Progress']").last.click

# Remove the first document
visit edit_partners_profile_path
find("button[data-bs-target='#attached_documents']").click
within "#attached_documents" do
document_name = "document1.md"
document_li = find("li.attached-document", text: document_name)
document_li.find("a.btn-danger", text: "Remove").click
expect(page).not_to have_selector("li.attached-document", text: document_name)
end

# Save Progress
all("input[type='submit'][value='Save Progress']").last.click
expect(page).to have_css(".alert-success", text: "Details were successfully updated.")

# Verify only one document is listed
visit edit_partners_profile_path
find("button[data-bs-target='#attached_documents']").click
within "#attached_documents" do
expect(page).to have_link("document2.md")
end
end

it "persists individual file upload when there are validation errors" do
# Open up Agency Information section and upload proof-of-status letter
find("button[data-bs-target='#agency_information']").click
within "#agency_information" do
Expand Down