const Blacklight = function() { const buffer = new Array; return { onLoad: function(func) { buffer.push(func); }, activate: function() { for(let i = 0; i < buffer.length; i++) { buffer[i].call(); } }, listeners: function () { const listeners = []; if (typeof Turbo !== 'undefined') { listeners.push('turbo:load', 'turbo:frame-load'); } else if (typeof Turbolinks !== 'undefined' && Turbolinks.supported) { // Turbolinks 5 if (Turbolinks.BrowserAdapter) { listeners.push('turbolinks:load'); } else { // Turbolinks < 5 listeners.push('page:load', 'DOMContentLoaded'); } } else { listeners.push('DOMContentLoaded'); } return listeners; } }; }(); // turbolinks triggers page:load events on page transition // If app isn't using turbolinks, this event will never be triggered, no prob. Blacklight.listeners().forEach(function(listener) { document.addEventListener(listener, function() { Blacklight.activate(); }); }); Blacklight.onLoad(function () { const elem = document.querySelector('.no-js'); // The "no-js" class may already have been removed because this function is // run on every turbo:load event, in that case, it won't find an element. if (!elem) return; elem.classList.remove('no-js'); elem.classList.add('js'); }); /* 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() */ 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'); } } } 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(); })(); const ButtonFocus = (() => { document.addEventListener('click', (e) => { // Button clicks should change focus. As of 10/3/19, Firefox for Mac and // Safari both do not set focus to a button on button click. // See https://zellwk.com/blog/inconsistent-button-behavior/ for background information if (e.target.matches('[data-toggle="collapse"]') || e.target.matches('[data-bs-toggle="collapse"]')) { e.target.focus(); } }); })(); /* 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 `
Some message
<%= link_to "This result will still be within modal", some_link, data: { blacklight_modal: "preserve" } %>Expected a successful response from the server, but got an error
${this.type} ${this.url}\n${jqXHR.status}: ${errorThrown}