From c372237b98f6cfa2aced12737ce4f539a30117d2 Mon Sep 17 00:00:00 2001 From: Chris Beer Date: Mon, 6 Feb 2023 12:52:27 -0800 Subject: [PATCH] Use stimulus for bookmark toggling --- .../document/bookmark_component.html.erb | 15 ++-- app/javascript/blacklight/bookmark_toggle.js | 19 ----- app/javascript/blacklight/checkbox_submit.js | 80 ------------------- app/javascript/blacklight/index.js | 2 - .../blacklight/checkbox_submit_controller.js | 63 +++++++++++++++ config/importmap.rb | 1 + lib/railties/blacklight.rake | 15 ++++ package.json | 1 + 8 files changed, 88 insertions(+), 108 deletions(-) delete mode 100644 app/javascript/blacklight/bookmark_toggle.js delete mode 100644 app/javascript/blacklight/checkbox_submit.js create mode 100644 app/javascript/controllers/blacklight/checkbox_submit_controller.js 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..82f89abb0d 100644 --- a/lib/railties/blacklight.rake +++ b/lib/railties/blacklight.rake @@ -119,3 +119,18 @@ namespace :blacklight do end end end + +if Rake::Task.task_defined?('stimulus:manifest:display') + 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 +end + +if Rake::Task.task_defined?('stimulus:manifest:update') + 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 +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" } }