Skip to content

feat: replace blueimp jQuery-File-Upload with a dependency-free chunked uploader#7552

Closed
orangewolf wants to merge 1 commit into
samvera:mainfrom
orangewolf:feat/modern-uploader
Closed

feat: replace blueimp jQuery-File-Upload with a dependency-free chunked uploader#7552
orangewolf wants to merge 1 commit into
samvera:mainfrom
orangewolf:feat/modern-uploader

Conversation

@orangewolf

Copy link
Copy Markdown
Member

Summary

Replaces the vendored, unversioned blueimp jQuery-File-Upload stack with Hyrax.Uploader — a small dependency-free ES6 module (~450 lines, no jQuery in the core) served through the existing Sprockets/almond pipeline. Net −2,300 lines. Part of the upload-unification chain, but independent: based on main, server contract untouched — it merges before or after the Active Storage PRs (#7547#7551) equally well.

What's preserved (feature-for-feature)

Surface Behavior
Work new/edit Files tab multi-file, folder upload (webkitdirectory), drag & drop, per-file progress bar, cancel/remove, error display
Batch upload per-file title + resource type inputs (title[<id>], resource_type[<id>][]), "set all to this resource type"
File-set Versions tab single-file upload, replace-on-reselect, files_files[]
Collection branding banner (single file; clears current banner + its banner_unchanged input), logos (append, per-logo linkurl[]/alttext[] kept positionally aligned)
SaveWorkControl unchanged — the uploader fires the legacy jQuery event names (fileuploadadded/-completed/-destroyed/-start/-stop/-fail) alongside hyrax:uploads:* CustomEvents
Protocol same two-step staged upload: POST /uploads (filename → Hyrax::UploadedFile id) then bytes under that id in sequential Content-Range chunks above maxChunkSize (10 MB) — still Cloudflare-request-size-safe; Hyrax.config.uploader limits honored client-side; DELETE on remove

Improvements

  • Row markup moves from blueimp x-tmpl scripts to server-rendered <template> elements (ERB keeps i18n + per-page variants; apps override partials as usual).
  • Row form inputs are authored nameless and named on completion — an in-flight upload can no longer satisfy the required-files gate or submit partial values (e.g. a misaligned linkurl[]).
  • uploaded_files.es6 now decrements its in-progress counter on fileuploadfail — fixing the legacy stuck Save button after a canceled/failed upload.
  • One less unversionable vendored dependency; configuration collapses to Hyrax.config.uploader + data-* attributes (no more four-way config split).

Notes for reviewers

  • blueimp's standalone tmpl.js helper stays (the permissions and relationships editors use it; now required explicitly in hyrax.js).
  • The upload widget is an AMD module like the rest of Hyrax's ES6, so its init moved after almond in hyrax.js.
  • The Versions tab still emits files_files[] so this PR stands alone; once feat: unify staged upload params behind an ownership-enforcing resolver #7551 (canonical uploaded_files[] + deprecation) lands, flipping the template's field name is a one-liner.
  • Not carried over from blueimp: cross-page-load resumable uploads (never wired up in Hyrax) and the iframe transport (IE fallback).

Tests (run locally against the koppie test app)

  • karma/jasmine, headless Chrome, against the real compiled asset bundle: 91 of 91 pass — new spec/javascripts/hyrax_uploader_spec.js (pre-create + upload + row fill; sequential Content-Range chunk math 0-9/25, 10-19/25, 20-24/25; legacy event ordering for the save gate; maxFileSize rejection) plus the entire pre-existing JS suite (which also proves the almond/require load order stays intact).
  • View specs green: hyrax/base/_form_files, hyrax/batch_uploads/_form, hyrax/file_sets/_versioning, hyrax/dashboard/collections/_form_branding, hyrax/uploads/create.json.jbuilder.
  • rails assets:precompile succeeds with the new module.
  • Browser feature specs (js: true) were not run locally (the capybara setup targets a remote selenium grid); CI's dockerized matrix covers them — called out honestly.

🤖 Generated with Claude Code

Replaces the vendored, unversioned blueimp jQuery-File-Upload stack
(jquery.fileupload*.js, jquery.iframe-transport.js) and its tmpl-based
row rendering with Hyrax.Uploader, a small ES6 module with no jQuery
dependency, served through the existing Sprockets/almond pipeline.

The server contract is untouched: the widget speaks the same two-step
staged upload protocol (POST /uploads with the filename to create the
Hyrax::UploadedFile, then the bytes under that id, split into
sequential Content-Range chunks above maxChunkSize — still
Cloudflare-request-size-safe), injects the same hidden fields
(uploaded_files[], files_files[], banner_files[], logo_files[],
linkurl[]/alttext[]), honors Hyrax.config.uploader limits client-side,
and DELETEs staged records on remove.

Covered surfaces, matching the old widget: work new/edit Files tab
(multi-file, folder upload via webkitdirectory, drag and drop, per-file
progress/cancel/remove/errors), batch upload with per-file
title/resource type inputs, file set version upload (single file,
replace-on-reselect), collection branding banner (single file,
clears the current banner and its banner_unchanged input) and logos
(with per-logo link/alt inputs appended positionally).

Row markup moves from blueimp x-tmpl scripts to server-rendered
<template> elements (ERB keeps i18n and per-page variants); form
inputs in rows are authored nameless and named on completion, so an
in-flight upload can neither satisfy the required-files gate nor
submit partial values. SaveWorkControl keeps working unchanged: the
uploader fires the legacy jQuery event names (fileuploadadded/
-completed/-destroyed/-start/-stop, plus -fail) alongside
hyrax:uploads:* CustomEvents; uploaded_files.es6 now also decrements
its in-progress counter on fileuploadfail, fixing the legacy stuck
Save button after a canceled or failed upload.

blueimp's standalone tmpl.js helper stays vendored: the permissions
and relationships editors use it (now loaded explicitly from
hyrax.js). The upload widget initialization moved after almond since
the uploader is an AMD module like the rest of Hyrax's ES6.

Tests, run locally against the koppie test app:
- karma/jasmine (headless Chrome against the compiled asset bundle):
  91 of 91 pass — the new spec/javascripts/hyrax_uploader_spec.js
  (pre-create + upload + row fill, sequential Content-Range chunk
  math, legacy event ordering for the save gate, maxFileSize
  rejection) plus the entire pre-existing JS suite
- view specs: hyrax/base/_form_files, hyrax/batch_uploads/_form,
  hyrax/file_sets/_versioning, hyrax/dashboard/collections/
  _form_branding, hyrax/uploads/create.json.jbuilder — green
- assets:precompile succeeds with the new module
- browser feature specs (js: true) not run locally — the capybara
  setup targets a remote selenium grid; CI's dockerized matrix covers
  them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

var logo = document.getElementById('fileuploadlogo');
if (logo && !logo.hyraxUploader) {
new Hyrax.Uploader(logo, { rowTemplate: 'hyrax-upload-row-logo' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do not use 'new' for side effects no-new

}

var main = document.getElementById('fileupload');
if (main && !main.hyraxUploader) { new Hyrax.Uploader(main, {}); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do not use 'new' for side effects no-new

// expose the uploader for view-level initializers and overrides
window.Hyrax = window.Hyrax || {};
if (!window.Hyrax.Uploader) {
window.Hyrax.Uploader = require('hyrax/uploader').Uploader;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected require() global-require

}

// finalize a row: reveal the hidden id input, wire deletion
uploadCompleted(upload, record) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Method 'uploadCompleted' has a complexity of 8 complexity


// ---- validation --------------------------------------------------------

validate(file) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Method 'validate' has a complexity of 8 complexity


// ---- queue -------------------------------------------------------------

pump() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Method 'pump' has a complexity of 8 complexity

class Uploader {
// @param element [HTMLElement] the widget container (e.g. #fileupload)
// @param options [Object] overrides merged over Hyrax.config.uploader
constructor(element, options) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Constructor has a complexity of 11 complexity

@@ -0,0 +1,135 @@
describe("Hyrax.Uploader", function() {
var Uploader = require('hyrax/uploader').Uploader;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected require() global-require

@github-actions

Copy link
Copy Markdown

Test Results

    1 files   -     16      1 suites   - 16   0s ⏱️ - 3h 19m 30s
1 921 tests  -  6 036  1 921 ✅  -  5 722  0 💤  - 307  0 ❌  - 7 
1 921 runs   - 25 269  1 921 ✅  - 24 663  0 💤  - 599  0 ❌  - 7 

Results for commit ceb0901. ± Comparison against base commit 16ddae4.

@orangewolf

Copy link
Copy Markdown
Member Author

Superseded by #7558 — same change moved to an in-repo branch (active-storage-for-all/modern-uploader) and retitled.

@orangewolf orangewolf closed this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants