diff --git a/assets/js/htmx-ext-preload@2.0.1.js b/assets/js/htmx-ext-preload@2.0.1.js new file mode 100644 index 0000000..5f6e1c0 --- /dev/null +++ b/assets/js/htmx-ext-preload@2.0.1.js @@ -0,0 +1,141 @@ +// This adds the "preload" extension to htmx. By default, this will +// preload the targets of any tags with `href` or `hx-get` attributes +// if they also have a `preload` attribute as well. See documentation +// for more details +htmx.defineExtension('preload', { + + onEvent: function(name, event) { + // Only take actions on "htmx:afterProcessNode" + if (name !== 'htmx:afterProcessNode') { + return + } + + // SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY + + // attr gets the closest non-empty value from the attribute. + var attr = function(node, property) { + if (node == undefined) { return undefined } + return node.getAttribute(property) || node.getAttribute('data-' + property) || attr(node.parentElement, property) + } + + // load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're + // preloading an htmx resource (this sends the same HTTP headers as a regular htmx request) + var load = function(node) { + // Called after a successful AJAX request, to mark the + // content as loaded (and prevent additional AJAX calls.) + var done = function(html) { + if (!node.preloadAlways) { + node.preloadState = 'DONE' + } + + if (attr(node, 'preload-images') == 'true') { + document.createElement('div').innerHTML = html // create and populate a node to load linked resources, too. + } + } + + return function() { + // If this value has already been loaded, then do not try again. + if (node.preloadState !== 'READY') { + return + } + + // Special handling for HX-GET - use built-in htmx.ajax function + // so that headers match other htmx requests, then set + // node.preloadState = TRUE so that requests are not duplicated + // in the future + var hxGet = node.getAttribute('hx-get') || node.getAttribute('data-hx-get') + if (hxGet) { + htmx.ajax('GET', hxGet, { + source: node, + handler: function(elt, info) { + done(info.xhr.responseText) + } + }) + return + } + + // Otherwise, perform a standard xhr request, then set + // node.preloadState = TRUE so that requests are not duplicated + // in the future. + if (node.getAttribute('href')) { + var r = new XMLHttpRequest() + r.open('GET', node.getAttribute('href')) + r.onload = function() { done(r.responseText) } + r.send() + } + } + } + + // This function processes a specific node and sets up event handlers. + // We'll search for nodes and use it below. + var init = function(node) { + // If this node DOES NOT include a "GET" transaction, then there's nothing to do here. + if (node.getAttribute('href') + node.getAttribute('hx-get') + node.getAttribute('data-hx-get') == '') { + return + } + + // Guarantee that we only initialize each node once. + if (node.preloadState !== undefined) { + return + } + + // Get event name from config. + var on = attr(node, 'preload') || 'mousedown' + const always = on.indexOf('always') !== -1 + if (always) { + on = on.replace('always', '').trim() + } + + // FALL THROUGH to here means we need to add an EventListener + + // Apply the listener to the node + node.addEventListener(on, function(evt) { + if (node.preloadState === 'PAUSE') { // Only add one event listener + node.preloadState = 'READY' // Required for the `load` function to trigger + + // Special handling for "mouseover" events. Wait 100ms before triggering load. + if (on === 'mouseover') { + window.setTimeout(load(node), 100) + } else { + load(node)() // all other events trigger immediately. + } + } + }) + + // Special handling for certain built-in event handlers + switch (on) { + case 'mouseover': + // Mirror `touchstart` events (fires immediately) + node.addEventListener('touchstart', load(node)) + + // WHhen the mouse leaves, immediately disable the preload + node.addEventListener('mouseout', function(evt) { + if ((evt.target === node) && (node.preloadState === 'READY')) { + node.preloadState = 'PAUSE' + } + }) + break + + case 'mousedown': + // Mirror `touchstart` events (fires immediately) + node.addEventListener('touchstart', load(node)) + break + } + + // Mark the node as ready to run. + node.preloadState = 'PAUSE' + node.preloadAlways = always + htmx.trigger(node, 'preload:init') // This event can be used to load content immediately. + } + + // Search for all child nodes that have a "preload" attribute + const parent = event.target || event.detail.elt; + parent.querySelectorAll("[preload]").forEach(function(node) { + // Initialize the node with the "preload" attribute + init(node) + + // Initialize all child elements that are anchors or have `hx-get` (use with care) + node.querySelectorAll('a,[hx-get],[data-hx-get]').forEach(init) + }) + } +}) diff --git a/assets/views/artwork.jet.html b/assets/views/artwork.jet.html index 39232a0..643ca98 100644 --- a/assets/views/artwork.jet.html +++ b/assets/views/artwork.jet.html @@ -3,7 +3,9 @@ {{- block body() }} +{* +*}