Ver Fonte

Ticket #4641 - Use HTMX to load page content without genaral page structure. Preload.

AntonLV há 2 anos atrás
pai
commit
67dcd8cd4b

+ 1 - 0
inc/classes/BxDolMenu.php

@@ -85,6 +85,7 @@ class BxDolMenu extends BxDolFactory implements iBxDolFactoryObject, iBxDolRepla
 
 
     protected $_bHx;
     protected $_bHx;
     protected $_bHxHead;
     protected $_bHxHead;
+    protected $_mHxPreload;
     protected $_aHx;
     protected $_aHx;
 
 
     protected $_bDynamicMode;
     protected $_bDynamicMode;

+ 7 - 4
inc/utils.inc.php

@@ -2240,17 +2240,20 @@ function bx_get_htmx_target ()
     return isset($_SERVER['HTTP_HX_TARGET']) ? $_SERVER['HTTP_HX_TARGET'] : false;
     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 '';
         return '';
 
 
+    if($mPreload === true)
+        $mPreload = 'mousedown';
+
     $aHxAttrs = [];
     $aHxAttrs = [];
-    array_walk($a, function($mixedValue, $sIndex) use (&$aHxAttrs) {
+    array_walk($aAttrs, function($mixedValue, $sIndex) use (&$aHxAttrs) {
         $aHxAttrs['hx-' . $sIndex] = $mixedValue;
         $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)
 function bx_idn_to_utf8($sUrl, $bReturnDomain = false)

+ 1 - 0
install/sql/system.sql

@@ -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', 'prism/prism.js', 1, 12),
 ('system', 'js_system', 'htmx/htmx.min.js', 1, 13),
 ('system', 'js_system', 'htmx/htmx.min.js', 1, 13),
 ('system', 'js_system', 'htmx/head-support.js', 1, 14),
 ('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', 'functions.js', 1, 20),
 ('system', 'js_system', 'jquery.webForms.js', 1, 21),
 ('system', 'js_system', 'jquery.webForms.js', 1, 21),
 ('system', 'js_system', 'jquery.dolPopup.js', 1, 22),
 ('system', 'js_system', 'jquery.dolPopup.js', 1, 22),

+ 9 - 2
modules/boonex/artificer/data/template/system/scripts/BxTemplMenu.php

@@ -23,6 +23,7 @@ class BxTemplMenu extends BxBaseMenu
 
 
                 $this->_bHx = true;
                 $this->_bHx = true;
                 $this->_bHxHead = true;
                 $this->_bHxHead = true;
+                $this->_mHxPreload = true;
                 $this->_aHx = [
                 $this->_aHx = [
                     'get' => '',
                     'get' => '',
                     'trigger' => 'click',
                     'trigger' => 'click',
@@ -33,9 +34,15 @@ class BxTemplMenu extends BxBaseMenu
                     'on::after-on-load' => 'oBxArtificerUtils.submenuClickAl(this)'
                     'on::after-on-load' => 'oBxArtificerUtils.submenuClickAl(this)'
                 ];
                 ];
 
 
-                $sInjection = 'hx-on::after-request="jQuery(this).bxProcessHtml()"';
+                $sExtensions = '';
                 if($this->_bHxHead)
                 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);
                 $this->_oTemplate->addInjection('injection_body', 'text', $sInjection);
             }
             }

+ 147 - 0
plugins_public/htmx/preload.js

@@ -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)
+		})
+	}
+})

+ 1 - 1
template/scripts/BxBaseMenu.php

@@ -238,7 +238,7 @@ class BxBaseMenu extends BxDolMenu
 
 
         if($this->_bHx && !empty($a['link']) && strpos($a['link'], 'javascript:') === false) {
         if($this->_bHx && !empty($a['link']) && strpos($a['link'], 'javascript:') === false) {
             $this->_aHx['get'] = $a['link'];
             $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))
             if(!bx_is_htmx_request() && !$this->_isSelected($a))
                 $a['attrs_wrp'] .= bx_get_htmx_attrs([
                 $a['attrs_wrp'] .= bx_get_htmx_attrs([