diff --git a/app/components/blacklight/document/bookmark_component.html.erb b/app/components/blacklight/document/bookmark_component.html.erb
index 050a03815b..acd7bb9737 100644
--- a/app/components/blacklight/document/bookmark_component.html.erb
+++ b/app/components/blacklight/document/bookmark_component.html.erb
@@ -7,15 +7,16 @@
method: bookmarked? ? :delete : :put,
class: "bookmark-toggle",
data: {
- 'doc-id' => @document.id,
- present: t('blacklight.search.bookmarks.present'),
- absent: t('blacklight.search.bookmarks.absent'),
- inprogress: t('blacklight.search.bookmarks.inprogress')
+ controller: 'blacklight--checkbox-submit',
+ 'blacklight--checkbox-submit-present-value': t('blacklight.search.bookmarks.present'),
+ 'blacklight--checkbox-submit-absent-value': t('blacklight.search.bookmarks.absent'),
+ 'blacklight--checkbox-submit-inprogress-value': t('blacklight.search.bookmarks.inprogress'),
+ 'doc-id' => @document.id
}) do %>
-
diff --git a/app/javascript/blacklight/bookmark_toggle.js b/app/javascript/blacklight/bookmark_toggle.js
deleted file mode 100644
index dd32aae236..0000000000
--- a/app/javascript/blacklight/bookmark_toggle.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import Blacklight from 'blacklight/core'
-import CheckboxSubmit from 'blacklight/checkbox_submit'
-
-const BookmarkToggle = (() => {
- // change form submit toggle to checkbox
- Blacklight.doBookmarkToggleBehavior = function() {
- document.addEventListener('click', (e) => {
- if (e.target.matches('[data-checkboxsubmit-target="checkbox"]')) {
- const form = e.target.closest('form')
- if (form) new CheckboxSubmit(form).clicked(e);
- }
- });
- };
- Blacklight.doBookmarkToggleBehavior.selector = 'form.bookmark-toggle';
-
- Blacklight.doBookmarkToggleBehavior();
-})()
-
-export default BookmarkToggle
diff --git a/app/javascript/blacklight/checkbox_submit.js b/app/javascript/blacklight/checkbox_submit.js
deleted file mode 100644
index fd9ddb863f..0000000000
--- a/app/javascript/blacklight/checkbox_submit.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/* Converts a "toggle" form, with single submit button to add/remove
- something, like used for Bookmarks, into an AJAXy checkbox instead.
- Apply to a form. Does require certain assumption about the form:
- 1) The same form 'action' href must be used for both ADD and REMOVE
- actions, with the different being the hidden input name="_method"
- being set to "put" or "delete" -- that's the Rails method to pretend
- to be doing a certain HTTP verb. So same URL, PUT to add, DELETE
- to remove. This plugin assumes that.
- Plus, the form this is applied to should provide a data-doc-id
- attribute (HTML5-style doc-*) that contains the id/primary key
- of the object in question -- used by plugin for a unique value for
- DOM id's.
- Uses HTML for a checkbox compatible with Bootstrap 4.
- new CheckboxSubmit(document.querySelector('form.something')).render()
-*/
-export default class CheckboxSubmit {
- constructor(form) {
- this.form = form
- }
-
- async clicked(evt) {
- this.spanTarget.innerHTML = this.form.getAttribute('data-inprogress')
- this.labelTarget.setAttribute('disabled', 'disabled');
- this.checkboxTarget.setAttribute('disabled', 'disabled');
- const response = await fetch(this.formTarget.getAttribute('action'), {
- body: new FormData(this.formTarget),
- method: this.formTarget.getAttribute('method').toUpperCase(),
- headers: {
- 'Accept': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest',
- 'X-CSRF-Token': document.querySelector('meta[name=csrf-token]')?.content
- }
- })
- this.labelTarget.removeAttribute('disabled')
- this.checkboxTarget.removeAttribute('disabled')
- if (response.ok) {
- const json = await response.json()
- this.updateStateFor(!this.checked)
- document.querySelector('[data-role=bookmark-counter]').innerHTML = json.bookmarks.count
- } else {
- alert('Error')
- }
- }
-
- get checked() {
- return (this.form.querySelectorAll('input[name=_method][value=delete]').length != 0)
- }
-
- get formTarget() {
- return this.form
- }
-
- get labelTarget() {
- return this.form.querySelector('[data-checkboxsubmit-target="label"]')
- }
-
- get checkboxTarget() {
- return this.form.querySelector('[data-checkboxsubmit-target="checkbox"]')
- }
-
- get spanTarget() {
- return this.form.querySelector('[data-checkboxsubmit-target="span"]')
- }
-
- updateStateFor(state) {
- this.checkboxTarget.checked = state
-
- if (state) {
- this.labelTarget.classList.add('checked')
- //Set the Rails hidden field that fakes an HTTP verb
- //properly for current state action.
- this.formTarget.querySelector('input[name=_method]').value = 'delete'
- this.spanTarget.innerHTML = this.form.getAttribute('data-present')
- } else {
- this.labelTarget.classList.remove('checked')
- this.formTarget.querySelector('input[name=_method]').value = 'put'
- this.spanTarget.innerHTML = this.form.getAttribute('data-absent')
- }
- }
-}
diff --git a/app/javascript/blacklight/index.js b/app/javascript/blacklight/index.js
index 0fe8e22724..7fa42d29a6 100644
--- a/app/javascript/blacklight/index.js
+++ b/app/javascript/blacklight/index.js
@@ -1,11 +1,9 @@
-import BookmarkToggle from 'blacklight/bookmark_toggle'
import ButtonFocus from 'blacklight/button_focus'
import Modal from 'blacklight/modal'
import SearchContext from 'blacklight/search_context'
import Core from 'blacklight/core'
export default {
- BookmarkToggle,
ButtonFocus,
Modal,
SearchContext,
diff --git a/app/javascript/controllers/blacklight/checkbox_submit_controller.js b/app/javascript/controllers/blacklight/checkbox_submit_controller.js
new file mode 100644
index 0000000000..0b072e9c0a
--- /dev/null
+++ b/app/javascript/controllers/blacklight/checkbox_submit_controller.js
@@ -0,0 +1,63 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static targets = ['checkbox', 'label', 'span']
+ static values = { present: String, absent: String, inprogress: String }
+
+ async change() {
+ this.spanTarget.innerHTML = this.inprogressValue;
+ this.labelTarget.setAttribute('disabled', 'disabled');
+ this.checkboxTarget.setAttribute('disabled', 'disabled');
+
+ const response = await this.submit();
+
+ this.labelTarget.removeAttribute('disabled')
+ this.checkboxTarget.removeAttribute('disabled')
+
+ if (response.ok) {
+ const json = await response.json()
+ this.updateStateTo(this.checked)
+ this.updateGlobalBookmarkCounter(json.bookmarks.count)
+ } else {
+ alert('Error')
+ }
+ }
+
+ get checked() {
+ return this.checkboxTarget.checked;
+ }
+
+ async submit() {
+ const method = this.checked ? 'put' : 'delete';
+
+ //Set the Rails hidden field that fakes an HTTP verb
+ //properly for current state action.
+ this.element.querySelector('input[name=_method]').value = method;
+
+ const response = await fetch(this.element.getAttribute('action'), {
+ body: new FormData(this.element),
+ method: method.toUpperCase(),
+ headers: {
+ 'Accept': 'application/json',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-CSRF-Token': document.querySelector('meta[name=csrf-token]')?.content
+ }
+ })
+
+ return response;
+ }
+
+ updateGlobalBookmarkCounter(value) {
+ document.querySelector('[data-role=bookmark-counter]').innerHTML = value;
+ }
+
+ updateStateTo(state) {
+ if (state) {
+ this.labelTarget.classList.add('checked')
+ this.spanTarget.innerHTML = this.presentValue;
+ } else {
+ this.labelTarget.classList.remove('checked')
+ this.spanTarget.innerHTML = this.absentValue;
+ }
+ }
+}
diff --git a/config/importmap.rb b/config/importmap.rb
index a05a6f205c..500336d3ed 100644
--- a/config/importmap.rb
+++ b/config/importmap.rb
@@ -1,3 +1,4 @@
# frozen_string_literal: true
pin_all_from File.expand_path("../app/javascript/blacklight", __dir__), under: "blacklight"
+pin_all_from File.expand_path("../app/javascript/controllers", __dir__), under: "controllers"
diff --git a/lib/railties/blacklight.rake b/lib/railties/blacklight.rake
index 8238eb26f6..41e3d6dfc0 100644
--- a/lib/railties/blacklight.rake
+++ b/lib/railties/blacklight.rake
@@ -119,3 +119,14 @@ namespace :blacklight do
end
end
end
+
+Rake::Task['stimulus:manifest:display'].enhance do
+ puts Stimulus::Manifest.generate_from(Blacklight::Engine.root.join("app/javascript/controllers")).join("\n").gsub('./blacklight/', 'blacklight-frontend/app/javascript/controllers/blacklight/')
+end
+
+Rake::Task['stimulus:manifest:update'].enhance do
+ manifest = Stimulus::Manifest.generate_from(Blacklight::Engine.root.join("app/javascript/controllers")).join("\n").gsub('./blacklight/', 'blacklight-frontend/app/javascript/controllers/blacklight/')
+ File.open(Rails.root.join("app/javascript/controllers/index.js"), "a+") do |index|
+ index.puts manifest
+ end
+end
diff --git a/package.json b/package.json
index 2285cd18ee..c83dd016f1 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"not IE 11"
],
"dependencies": {
+ "@hotwired/stimulus": "^3.2.1",
"bootstrap": ">=4.3.1 <6.0.0"
}
}