Просмотр исходного кода

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

AntonLV 2 лет назад
Родитель
Сommit
5a387e821b

+ 1 - 0
inc/classes/BxDolMenu.php

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

+ 2 - 1
install/sql/system.sql

@@ -6256,7 +6256,8 @@ INSERT INTO `sys_preloader`(`module`, `type`, `content`, `active`, `order`) VALU
 ('system', 'js_system', 'headroom.min.js', 1, 10),
 ('system', 'js_system', 'at.js/js/jquery.atwho.min.js', 1, 11),
 ('system', 'js_system', 'prism/prism.js', 1, 12),
-('system', 'js_system', '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', 'functions.js', 1, 20),
 ('system', 'js_system', 'jquery.webForms.js', 1, 21),
 ('system', 'js_system', 'jquery.dolPopup.js', 1, 22),

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

@@ -22,16 +22,21 @@ class BxTemplMenu extends BxBaseMenu
                 $sClass = 'bx-menu-tab-active';
 
                 $this->_bHx = true;
+                $this->_bHxHead = true;
                 $this->_aHx = [
                     'get' => '',
                     'trigger' => 'click',
                     'target' => '#bx-content-wrapper',
                     'swap' => 'outerHTML settle:200ms',
-                    'replace-url' => 'true',
+                    'push-url' => 'true',
                     'on:htmx-after-on-load' => 'jQuery(this).parent().addClass(\'' . $sClass . '\').siblings().removeClass(\'' . $sClass . '\');'
                 ];
 
-                $this->_oTemplate->addInjection('injection_body', 'text', 'hx-on::after-request="jQuery(this).bxProcessHtml()"');
+                $sInjection = 'hx-on::after-request="jQuery(this).bxProcessHtml()"';
+                if($this->_bHxHead)
+                    $sInjection .= ' hx-ext="head-support"';
+
+                $this->_oTemplate->addInjection('injection_body', 'text', $sInjection);
             }
         }
 

+ 141 - 0
plugins_public/htmx/head-support.js

@@ -0,0 +1,141 @@
+//==========================================================
+// head-support.js
+//
+// An extension to htmx 1.0 to add head tag merging.
+//==========================================================
+(function(){
+
+    var api = null;
+
+    function log() {
+        //console.log(arguments);
+    }
+
+    function mergeHead(newContent, defaultMergeStrategy) {
+
+        if (newContent && newContent.indexOf('<head') > -1) {
+            const htmlDoc = document.createElement("html");
+            // remove svgs to avoid conflicts
+            var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
+            // extract head tag
+            var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
+
+            // if the  head tag exists...
+            if (headTag) {
+
+                var added = []
+                var removed = []
+                var preserved = []
+                var nodesToAppend = []
+
+                htmlDoc.innerHTML = headTag;
+                var newHeadTag = htmlDoc.querySelector("head");
+                var currentHead = document.head;
+
+                if (newHeadTag == null) {
+                    return;
+                } else {
+                    // put all new head elements into a Map, by their outerHTML
+                    var srcToNewHeadNodes = new Map();
+                    for (const newHeadChild of newHeadTag.children) {
+                        srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
+                    }
+                }
+
+
+
+                // determine merge strategy
+                var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
+
+                // get the current head
+                for (const currentHeadElt of currentHead.children) {
+
+                    // If the current head element is in the map
+                    var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
+                    var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
+                    var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
+                    if (inNewContent || isPreserved) {
+                        if (isReAppended) {
+                            // remove the current version and let the new version replace it and re-execute
+                            removed.push(currentHeadElt);
+                        } else {
+                            // this element already exists and should not be re-appended, so remove it from
+                            // the new content map, preserving it in the DOM
+                            srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
+                            preserved.push(currentHeadElt);
+                        }
+                    } else {
+                        if (mergeStrategy === "append") {
+                            // we are appending and this existing element is not new content
+                            // so if and only if it is marked for re-append do we do anything
+                            if (isReAppended) {
+                                removed.push(currentHeadElt);
+                                nodesToAppend.push(currentHeadElt);
+                            }
+                        } else {
+                            // if this is a merge, we remove this content since it is not in the new head
+                            if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
+                                removed.push(currentHeadElt);
+                            }
+                        }
+                    }
+                }
+
+                // Push the tremaining new head elements in the Map into the
+                // nodes to append to the head tag
+                nodesToAppend.push(...srcToNewHeadNodes.values());
+                log("to append: ", nodesToAppend);
+
+                for (const newNode of nodesToAppend) {
+                    log("adding: ", newNode);
+                    var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
+                    log(newElt);
+                    if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
+                        currentHead.appendChild(newElt);
+                        added.push(newElt);
+                    }
+                }
+
+                // remove all removed elements, after we have appended the new elements to avoid
+                // additional network requests for things like style sheets
+                for (const removedElement of removed) {
+                    if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
+                        currentHead.removeChild(removedElement);
+                    }
+                }
+
+                api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
+            }
+        }
+    }
+
+    htmx.defineExtension("head-support", {
+        init: function(apiRef) {
+            // store a reference to the internal API.
+            api = apiRef;
+
+            htmx.on('htmx:afterSwap', function(evt){
+                var serverResponse = evt.detail.xhr.response;
+                if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
+                    mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
+                }
+            })
+
+            htmx.on('htmx:historyRestore', function(evt){
+                if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
+                    if (evt.detail.cacheMiss) {
+                        mergeHead(evt.detail.serverResponse, "merge");
+                    } else {
+                        mergeHead(evt.detail.item.head, "merge");
+                    }
+                }
+            })
+
+            htmx.on('htmx:historyItemCreated', function(evt){
+                var historyItem = evt.detail.item;
+                historyItem.head = document.head.outerHTML;
+            })
+        }
+    });
+
+})()

+ 0 - 0
plugins_public/htmx.min.js → plugins_public/htmx/htmx.min.js


+ 4 - 0
template/page_58.html

@@ -1,3 +1,7 @@
+<head>
+    <title>__page_header__</title>
+    __meta_info__
+</head>
 <div id="bx-content-wrapper">
     <bx_include_auto:_page_content_beg.html />
     __page_main_code__