app/javascript/blacklight/modal.js in blacklight-7.40.0 vs app/javascript/blacklight/modal.js in blacklight-8.0.0.beta1
- old
+ new
@@ -21,16 +21,10 @@
the layout when a JS AJAX request is detected, OR the response
can include a `<div data-blacklight-modal="container">` -- only the contents
of the container will be placed inside the modal, the rest of the
page will be ignored.
- If you'd like to have a link or button that closes the modal,
- you can just add a `data-dismiss="modal"` to the link,
- standard Bootstrap convention. But you can also have
- an href on this link for non-JS contexts, we'll make sure
- inside the modal it closes the modal and the link is NOT followed.
-
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.
@@ -47,180 +41,121 @@
<%= link_to "This result will still be within modal", some_link, data: { blacklight_modal: "preserve" } %>
</div>
<div class="modal-footer">
- <%= link_to "Close the modal", request_done_path, class: "submit button dialog-close", data: { dismiss: "modal" } %>
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
- One additional feature. If the content returned from the AJAX modal load
- has an element with `data-blacklight-modal=close`, that will trigger the modal
- to be closed. And if this element includes a node with class "flash_messages",
- the flash-messages node will be added to the main page inside #main-flahses.
-
- == Events
-
- We'll send out an event 'loaded.blacklight.blacklight-modal' with the #blacklight-modal
- dialog as the target, right after content is loaded into the modal but before
- it is shown (if not already a shown modal). In an event handler, you can
- inspect loaded content by looking inside $(this). If you call event.preventDefault(),
- we won't 'show' the dialog (although it may already have been shown, you may want to
- $(this).modal("hide") if you want to ensure hidden/closed.
-
- The data-blacklight-modal=close behavior is implemented with this event, see for example.
+ 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 './core'
+import ModalForm from './modalForm'
-// We keep all our data in Blacklight.modal object.
-// Create lazily if someone else created first.
-if (Blacklight.modal === undefined) {
- Blacklight.modal = {};
-}
+const Modal = (() => {
+ // We keep all our data in Blacklight.modal object.
+ // Create lazily if someone else created first.
+ if (Blacklight.modal === undefined) {
+ Blacklight.modal = {};
+ }
-// a Bootstrap modal div that should be already on the page hidden
-Blacklight.modal.modalSelector = '#blacklight-modal';
+ const modal = Blacklight.modal
-// Trigger selectors identify forms or hyperlinks that should open
-// inside a modal dialog.
-Blacklight.modal.triggerLinkSelector = 'a[data-blacklight-modal~=trigger]';
-Blacklight.modal.triggerFormSelector = 'form[data-blacklight-modal~=trigger]';
+ // a Bootstrap modal div that should be already on the page hidden
+ modal.modalSelector = '#blacklight-modal';
-// 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.
-Blacklight.modal.preserveLinkSelector = Blacklight.modal.modalSelector + ' a[data-blacklight-modal~=preserve]';
+ // Trigger selectors identify forms or hyperlinks that should open
+ // inside a modal dialog.
+ modal.triggerLinkSelector = 'a[data-blacklight-modal~=trigger]';
-Blacklight.modal.containerSelector = '[data-blacklight-modal~=container]';
+ // 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]';
-Blacklight.modal.modalCloseSelector = '[data-blacklight-modal~=close]';
+ 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 extreme
-// network errors.
-Blacklight.modal.onFailure = function(jqXHR, textStatus, errorThrown) {
- console.error('Server error:', this.url, jqXHR.status, errorThrown);
+ // Called on fatal failure of ajax load, function returns content
+ // to show to user in modal. Right now called only for extreme
+ // network errors.
+ modal.onFailure = function (jqXHR, textStatus, errorThrown) {
+ console.error('Server error:', this.url, jqXHR.status, errorThrown);
- var contents = '<div class="modal-header">' +
- '<div class="modal-title">There was a problem with your request.</div>' +
- '<button type="button" class="blacklight-modal-close btn-close close" data-dismiss="modal" aria-label="Close">' +
- ' <span aria-hidden="true">×</span>' +
- '</button></div>' +
- ' <div class="modal-body"><p>Expected a successful response from the server, but got an error</p>' +
- '<pre>' +
- this.type + ' ' + this.url + "\n" + jqXHR.status + ': ' + errorThrown +
- '</pre></div>';
- $(Blacklight.modal.modalSelector).find('.modal-content').html(contents);
- Blacklight.modal.show();
-}
+ const contents = `<div class="modal-header">
+ <div class="modal-title">There was a problem with your request.</div>
+ <button type="button" class="blacklight-modal-close btn-close close" data-dismiss="modal" data-bs-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <p>Expected a successful response from the server, but got an error</p>
+ <pre>${this.type} ${this.url}\n${jqXHR.status}: ${errorThrown}</pre>
+ </div>`
-Blacklight.modal.receiveAjax = function (contents) {
- // does it have a data- selector for container?
- // important we don't execute script tags, we shouldn't.
- // code modelled off of JQuery ajax.load. https://github.com/jquery/jquery/blob/main/src/ajax/load.js?source=c#L62
- var container = $('<div>').
- append( jQuery.parseHTML(contents) ).find( Blacklight.modal.containerSelector ).first();
- if (container.length !== 0) {
- contents = container.html();
- }
+ document.querySelector(`${modal.modalSelector} .modal-content`).innerHTML = contents
- $(Blacklight.modal.modalSelector).find('.modal-content').html(contents);
+ modal.show();
+ }
- // send custom event with the modal dialog div as the target
- var e = $.Event('loaded.blacklight.blacklight-modal')
- $(Blacklight.modal.modalSelector).trigger(e);
- // if they did preventDefault, don't show the dialog
- if (e.isDefaultPrevented()) return;
+ // 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")
+ const elements = dom.querySelectorAll(`${modal.containerSelector} > *`)
+ document.querySelector(`${modal.modalSelector} .modal-content`).replaceChildren(...elements)
- Blacklight.modal.show();
-};
+ modal.show();
+ };
-Blacklight.modal.modalAjaxLinkClick = function(e) {
- e.preventDefault();
-
- $.ajax({
- url: $(this).attr('href')
- })
- .fail(Blacklight.modal.onFailure)
- .done(Blacklight.modal.receiveAjax)
-};
-
-Blacklight.modal.modalAjaxFormSubmit = function(e) {
+ 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))
+ };
- $.ajax({
- url: $(this).attr('action'),
- data: $(this).serialize(),
- type: $(this).attr('method') // POST
+ 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.matches(`${modal.triggerLinkSelector}, ${modal.preserveLinkSelector}`))
+ modal.modalAjaxLinkClick(e)
+ else if (e.target.matches('[data-bl-dismiss="modal"]'))
+ modal.hide()
})
- .fail(Blacklight.modal.onFailure)
- .done(Blacklight.modal.receiveAjax)
-}
+ };
+ modal.hide = function (el) {
+ const dom = document.querySelector(Blacklight.modal.modalSelector)
-
-Blacklight.modal.setupModal = function() {
- // Event indicating blacklight is setting up a modal link,
- // you can catch it and call e.preventDefault() to abort
- // setup.
- var e = $.Event('setup.blacklight.blacklight-modal');
- $('body').trigger(e);
- if (e.isDefaultPrevented()) return;
-
- // 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.
- $('body').on('click', Blacklight.modal.triggerLinkSelector + ', ' + Blacklight.modal.preserveLinkSelector,
- Blacklight.modal.modalAjaxLinkClick);
- $('body').on('submit', Blacklight.modal.triggerFormSelector + ', ' + Blacklight.modal.preserveFormSelector,
- Blacklight.modal.modalAjaxFormSubmit);
-
- // Catch our own custom loaded event to implement data-blacklight-modal=closed
- $('body').on('loaded.blacklight.blacklight-modal', Blacklight.modal.checkCloseModal);
-
- // we support doing data-dismiss=modal on a <a> with a href for non-ajax
- // use, we need to suppress following the a's href that's there for
- // non-JS contexts.
- $('body').on('click', Blacklight.modal.modalSelector + ' a[data-dismiss~=modal]', function (e) {
- e.preventDefault();
- });
-};
-
-// A function used as an event handler on loaded.blacklight.blacklight-modal
-// to catch contained data-blacklight-modal=closed directions
-Blacklight.modal.checkCloseModal = function(event) {
- if ($(event.target).find(Blacklight.modal.modalCloseSelector).length) {
- var modalFlashes = $(this).find('.flash_messages');
-
- Blacklight.modal.hide(event.target);
- event.preventDefault();
-
- var mainFlashes = $('#main-flashes');
- mainFlashes.append(modalFlashes);
- modalFlashes.fadeIn(500);
+ if (!dom.open) return
+ dom.close()
}
-}
-Blacklight.modal.hide = function(el) {
- if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal !== 'undefined' && bootstrap.Modal.VERSION >= "5") {
- bootstrap.Modal.getOrCreateInstance(el || document.querySelector(Blacklight.modal.modalSelector)).hide();
- } else {
- $(el || Blacklight.modal.modalSelector).modal('hide');
- }
-}
+ modal.show = function(el) {
+ const dom = document.querySelector(Blacklight.modal.modalSelector)
-Blacklight.modal.show = function(el) {
- if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal !== 'undefined' && bootstrap.Modal.VERSION >= "5") {
- bootstrap.Modal.getOrCreateInstance(el || document.querySelector(Blacklight.modal.modalSelector)).show();
- } else {
- $(el || Blacklight.modal.modalSelector).modal('show');
+ if (dom.open) return
+ dom.showModal()
}
-}
-Blacklight.onLoad(function() {
- Blacklight.modal.setupModal();
-});
+ modal.setupModal()
+})()
+
+export default Modal