Skip to content

Commit

Permalink
Ticket #4641 - Use HTMX to load page content without genaral page st…
Browse files Browse the repository at this point in the history
…ructure. Preload.
  • Loading branch information
AntonLV committed Mar 18, 2024
1 parent e9d75f0 commit 67dcd8c
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 7 deletions.
1 change: 1 addition & 0 deletions inc/classes/BxDolMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class BxDolMenu extends BxDolFactory implements iBxDolFactoryObject, iBxDolRepla

protected $_bHx;
protected $_bHxHead;
protected $_mHxPreload;
protected $_aHx;

protected $_bDynamicMode;
Expand Down
11 changes: 7 additions & 4 deletions inc/utils.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -2240,17 +2240,20 @@ function bx_get_htmx_target ()
return isset($_SERVER['HTTP_HX_TARGET']) ? $_SERVER['HTTP_HX_TARGET'] : false;
}

function bx_get_htmx_attrs ($a)
function bx_get_htmx_attrs ($aAttrs, $mPreload = false)
{
if(!$a)
if(!$aAttrs)
return '';

if($mPreload === true)
$mPreload = 'mousedown';

$aHxAttrs = [];
array_walk($a, function($mixedValue, $sIndex) use (&$aHxAttrs) {
array_walk($aAttrs, function($mixedValue, $sIndex) use (&$aHxAttrs) {
$aHxAttrs['hx-' . $sIndex] = $mixedValue;
});

return bx_convert_array2attrs($aHxAttrs);
return bx_convert_array2attrs($aHxAttrs) . ($mPreload ? ' preload="' . $mPreload . '"' : '');
}

function bx_idn_to_utf8($sUrl, $bReturnDomain = false)
Expand Down
1 change: 1 addition & 0 deletions install/sql/system.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6258,6 +6258,7 @@ INSERT INTO `sys_preloader`(`module`, `type`, `content`, `active`, `order`) VALU
('system', 'js_system', 'prism/prism.js', 1, 12),
('system', 'js_system', 'htmx/htmx.min.js', 1, 13),
('system', 'js_system', 'htmx/head-support.js', 1, 14),
('system', 'js_system', 'htmx/preload.js', 1, 15),
('system', 'js_system', 'functions.js', 1, 20),
('system', 'js_system', 'jquery.webForms.js', 1, 21),
('system', 'js_system', 'jquery.dolPopup.js', 1, 22),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public function __construct ($aObject, $oTemplate = false)

$this->_bHx = true;
$this->_bHxHead = true;
$this->_mHxPreload = true;
$this->_aHx = [
'get' => '',
'trigger' => 'click',
Expand All @@ -33,9 +34,15 @@ public function __construct ($aObject, $oTemplate = false)
'on::after-on-load' => 'oBxArtificerUtils.submenuClickAl(this)'
];

$sInjection = 'hx-on::after-request="jQuery(this).bxProcessHtml()"';
$sExtensions = '';
if($this->_bHxHead)
$sInjection .= ' hx-ext="head-support"';
$sExtensions .= ' head-support';
if($this->_mHxPreload)
$sExtensions .= ' preload';

$sInjection = 'hx-on::after-request="jQuery(this).bxProcessHtml()"';
if(($sExtensions = trim($sExtensions)) != '')
$sInjection .= ' hx-ext="' . $sExtensions . '"';

$this->_oTemplate->addInjection('injection_body', 'text', $sInjection);
}
Expand Down
147 changes: 147 additions & 0 deletions plugins_public/htmx/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// 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();
return;
}
}
}

// 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
event.target.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)
})
}
})
2 changes: 1 addition & 1 deletion template/scripts/BxBaseMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ protected function _getMenuItem ($a)

if($this->_bHx && !empty($a['link']) && strpos($a['link'], 'javascript:') === false) {
$this->_aHx['get'] = $a['link'];
$a['attrs'] .= bx_get_htmx_attrs($this->_aHx);
$a['attrs'] .= bx_get_htmx_attrs($this->_aHx, $this->_mHxPreload);

if(!bx_is_htmx_request() && !$this->_isSelected($a))
$a['attrs_wrp'] .= bx_get_htmx_attrs([
Expand Down

0 comments on commit 67dcd8c

Please sign in to comment.