From c5f40fdbb36c60aca96eb58ecde3bbcb8d1da4c2 Mon Sep 17 00:00:00 2001 From: Chris Beer Date: Mon, 6 Feb 2023 14:15:04 -0800 Subject: [PATCH] Replace custom modal implementation with turbo/stimulus --- .../blacklight/document/action_component.rb | 2 +- .../blacklight/facet_field_component.html.erb | 2 +- .../facet_field_list_component.html.erb | 2 +- .../system/modal_component.html.erb | 42 ++--- app/javascript/blacklight/index.js | 2 - app/javascript/blacklight/modal.js | 166 ------------------ app/javascript/blacklight/modalForm.js | 60 ------- .../blacklight/modal_controller.js | 17 ++ app/views/catalog/facet.html.erb | 2 +- app/views/layouts/blacklight/base.html.erb | 4 +- app/views/shared/_modal.html.erb | 4 +- 11 files changed, 47 insertions(+), 256 deletions(-) delete mode 100644 app/javascript/blacklight/modal.js delete mode 100644 app/javascript/blacklight/modalForm.js create mode 100644 app/javascript/controllers/blacklight/modal_controller.js diff --git a/app/components/blacklight/document/action_component.rb b/app/components/blacklight/document/action_component.rb index 207726812d..3e9acee094 100644 --- a/app/components/blacklight/document/action_component.rb +++ b/app/components/blacklight/document/action_component.rb @@ -31,7 +31,7 @@ def link_to_modal_control url, id: @id, class: @link_classes, - data: {}.merge(({ blacklight_modal: "trigger", turbo: false } if @action.modal != false) || {}) + data: {}.merge(({ action: 'blacklight--modal#open', 'turbo-frame': 'modal' } if @action.modal != false) || {}) end def render_partial diff --git a/app/components/blacklight/facet_field_component.html.erb b/app/components/blacklight/facet_field_component.html.erb index bca981f5c0..2209740139 100644 --- a/app/components/blacklight/facet_field_component.html.erb +++ b/app/components/blacklight/facet_field_component.html.erb @@ -20,7 +20,7 @@
<%= link_to t("more_#{@facet_field.key}_html", scope: 'blacklight.search.facets', default: :more_html, field_name: @facet_field.label), @facet_field.modal_path, - data: { blacklight_modal: 'trigger', turbo: false } %> + data: { action: 'blacklight--modal#open', 'turbo-frame': 'modal' } %>
<% end %> diff --git a/app/components/blacklight/facet_field_list_component.html.erb b/app/components/blacklight/facet_field_list_component.html.erb index 39989ec3e9..57d5c61622 100644 --- a/app/components/blacklight/facet_field_list_component.html.erb +++ b/app/components/blacklight/facet_field_list_component.html.erb @@ -12,7 +12,7 @@
<%= link_to t("more_#{@facet_field.key}_html", scope: 'blacklight.search.facets', default: :more_html, field_name: @facet_field.label), @facet_field.modal_path, - data: { blacklight_modal: 'trigger' } %> + data: { action: 'blacklight--modal#open', 'turbo-frame': 'modal' } %>
<% end %> <% end %> diff --git a/app/components/blacklight/system/modal_component.html.erb b/app/components/blacklight/system/modal_component.html.erb index c6a32a222c..6b28512ee9 100644 --- a/app/components/blacklight/system/modal_component.html.erb +++ b/app/components/blacklight/system/modal_component.html.erb @@ -1,25 +1,27 @@ -
- <%= prefix %> + +
+ <%= prefix %> - +
diff --git a/app/javascript/blacklight/index.js b/app/javascript/blacklight/index.js index 7fa42d29a6..9db67df627 100644 --- a/app/javascript/blacklight/index.js +++ b/app/javascript/blacklight/index.js @@ -1,11 +1,9 @@ import ButtonFocus from 'blacklight/button_focus' -import Modal from 'blacklight/modal' import SearchContext from 'blacklight/search_context' import Core from 'blacklight/core' export default { ButtonFocus, - Modal, SearchContext, Core, onLoad: Core.onLoad diff --git a/app/javascript/blacklight/modal.js b/app/javascript/blacklight/modal.js deleted file mode 100644 index 19754ca6cf..0000000000 --- a/app/javascript/blacklight/modal.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - The blacklight modal plugin can display some interactions inside a Bootstrap - modal window, including some multi-page interactions. - - It supports unobtrusive Javascript, where a link or form that would have caused - a new page load is changed to display it's results inside a modal dialog, - by this plugin. The plugin assumes there is a Bootstrap modal div - on the page with id #blacklight-modal to use as the modal -- the standard Blacklight - layout provides this. - - To make a link or form have their results display inside a modal, add - `data-blacklight-modal="trigger"` to the link or form. (Note, form itself not submit input) - With Rails link_to helper, you'd do that like: - - link_to something, link, data: { blacklight_modal: "trigger" } - - The results of the link href or form submit will be displayed inside - a modal -- they should include the proper HTML markup for a bootstrap modal's - contents. Also, you ordinarily won't want the Rails template with wrapping - navigational elements to be used. The Rails controller could suppress - the layout when a JS AJAX request is detected, OR the response - can include a `
` -- only the contents - of the container will be placed inside the modal, the rest of the - page will be ignored. - - Link or forms inside the modal will ordinarily cause page loads - when they are triggered. However, if you'd like their results - to stay within the modal, just add `data-blacklight-modal="preserve"` - to the link or form. - - Here's an example of what might be returned, demonstrating most of the devices available: - -
- - - - - - -
- - - One additional feature. If the content returned from the AJAX form submission - can be a turbo-stream that defines some HTML fragementsand where on the page to put them: - https://turbo.hotwired.dev/handbook/streams -*/ -import Blacklight from 'blacklight/core' -import ModalForm from 'blacklight/modalForm' - -const Modal = (() => { - // We keep all our data in Blacklight.modal object. - // Create lazily if someone else created first. - if (Blacklight.modal === undefined) { - Blacklight.modal = {}; - } - - const modal = Blacklight.modal - - // a Bootstrap modal div that should be already on the page hidden - modal.modalSelector = '#blacklight-modal'; - - // Trigger selectors identify forms or hyperlinks that should open - // inside a modal dialog. - modal.triggerLinkSelector = 'a[data-blacklight-modal~=trigger]'; - - // preserve selectors identify forms or hyperlinks that, if activated already - // inside a modal dialog, should have destinations remain inside the modal -- but - // won't trigger a modal if not already in one. - // - // No need to repeat selectors from trigger selectors, those will already - // be preserved. MUST be manually prefixed with the modal selector, - // so they only apply to things inside a modal. - modal.preserveLinkSelector = modal.modalSelector + ' a[data-blacklight-modal~=preserve]'; - - modal.containerSelector = '[data-blacklight-modal~=container]'; - - // Called on fatal failure of ajax load, function returns content - // to show to user in modal. Right now called only for network errors. - modal.onFailure = function (error) { - console.error('Server error:', this.url, error); - - const contents = ` - ` - - document.querySelector(`${modal.modalSelector} .modal-content`).innerHTML = contents - - modal.show(); - } - - // Add the passed in contents to the modal and display it. - modal.receiveAjax = function (contents) { - const domparser = new DOMParser(); - const dom = domparser.parseFromString(contents, "text/html") - // If there is a containerSelector on the document, use its children. - let elements = dom.querySelectorAll(`${modal.containerSelector} > *`) - if (elements.length == 0) { - // If the containerSelector wasn't found, use the whole document - elements = dom.body.childNodes - } - - document.querySelector(`${modal.modalSelector} .modal-content`).replaceChildren(...elements) - - modal.show(); - }; - - - modal.modalAjaxLinkClick = function(e) { - e.preventDefault(); - const href = e.target.getAttribute('href') - fetch(href) - .then(response => { - if (!response.ok) { - throw new TypeError("Request failed"); - } - return response.text(); - }) - .then(data => modal.receiveAjax(data)) - .catch(error => modal.onFailure(error)) - }; - - modal.setupModal = function() { - // Register both trigger and preserve selectors in ONE event handler, combining - // into one selector with a comma, so if something matches BOTH selectors, it - // still only gets the event handler called once. - document.addEventListener('click', (e) => { - if (e.target.closest(`${modal.triggerLinkSelector}, ${modal.preserveLinkSelector}`)) - modal.modalAjaxLinkClick(e) - else if (e.target.closest('[data-bl-dismiss="modal"]')) - modal.hide() - }) - }; - - modal.hide = function (el) { - const dom = document.querySelector(Blacklight.modal.modalSelector) - - if (!dom.open) return - dom.close() - } - - modal.show = function(el) { - const dom = document.querySelector(Blacklight.modal.modalSelector) - - if (dom.open) return - dom.showModal() - } - - modal.setupModal() -})() - -export default Modal diff --git a/app/javascript/blacklight/modalForm.js b/app/javascript/blacklight/modalForm.js deleted file mode 100644 index d68db1d1b1..0000000000 --- a/app/javascript/blacklight/modalForm.js +++ /dev/null @@ -1,60 +0,0 @@ -// The email and sms forms are displayed inside a modal. When the form is submitted, -// this script closes the modal and puts the output on the main part of the page. -// By default it is rendering catalog/sms_success and catalog/email_succcess. -// These templates deliver a payload of the format used by turbo-streams. -// See https://turbo.hotwired.dev/handbook/streams -// That format allows a downstream application to override the template to define -// multiple customizable areas of the page to get updated. -export default class { - constructor(errorHandler, badRequestHandler, hideModal) { - this.errorHandler = errorHandler - this.badRequestHandler = badRequestHandler - this.hideModal = hideModal - } - - get triggerFormSelector() { - return 'form[data-blacklight-modal~=trigger]' - } - - bind() { - document.addEventListener('submit', (e) => { - if (e.target.matches(this.triggerFormSelector)) - this.onSubmit(e) - }) - } - - // This is like a light-weight version of turbo that only supports append presently. - updateTurboStream(data) { - this.hideModal() - const domparser = new DOMParser(); - const dom = domparser.parseFromString(data, "text/html") - dom.querySelectorAll("turbo-stream[action='append']").forEach((node) => { - const target = node.getAttribute('target') - const element = document.getElementById(target) - if (element) - element.append(node.querySelector('template').content.cloneNode(true)) - else - console.error(`Unable to find an element on the page with and ID of "${target}""`) - }) - } - - onSubmit(e) { - e.preventDefault(); - const form = e.target - fetch(form.action, { - body: new FormData(form), - headers: { "X-Requested-With": "XMLHttpRequest" }, // Ensures rails will return true when checking request.xhr? - method: form.method, - }) - .then(response => { - if (response.status === 422) { - return response.text().then(content => this.badRequestHandler(content) ) - } - if (!response.ok) { - throw new TypeError("Request failed"); - } - response.text().then(content => this.updateTurboStream(content)) - }) - .catch(error => this.errorHandler(error)) - } -} diff --git a/app/javascript/controllers/blacklight/modal_controller.js b/app/javascript/controllers/blacklight/modal_controller.js new file mode 100644 index 0000000000..e46b08638a --- /dev/null +++ b/app/javascript/controllers/blacklight/modal_controller.js @@ -0,0 +1,17 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ['content', 'modal'] + + connect() { + this.contentTarget.innerHTML = ''; + } + + open() { + this.modalTarget.showModal(); + } + + close() { + this.modalTarget.close(); + } +} diff --git a/app/views/catalog/facet.html.erb b/app/views/catalog/facet.html.erb index 0d0745c83e..2d10a61bae 100644 --- a/app/views/catalog/facet.html.erb +++ b/app/views/catalog/facet.html.erb @@ -9,7 +9,7 @@ <%= render partial: 'facet_index_navigation' if @facet.index_range && @display_facet.index? %> -
+
<%= render Blacklight::FacetComponent.new(display_facet: @display_facet, blacklight_config: blacklight_config, layout: false) %> diff --git a/app/views/layouts/blacklight/base.html.erb b/app/views/layouts/blacklight/base.html.erb index af832b1b0a..60f2211bfb 100644 --- a/app/views/layouts/blacklight/base.html.erb +++ b/app/views/layouts/blacklight/base.html.erb @@ -19,7 +19,7 @@ <%= javascript_importmap_tags %> <% elsif defined? Propshaft %> <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> - <% else %> + <% else %> <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> <%= javascript_include_tag "blacklight/blacklight", type: 'module' %> <% end %> @@ -30,7 +30,7 @@ import githubAutoCompleteElement from 'https://cdn.skypack.dev/@github/auto-complete-element'; - +