Browse code

Initial htmx npm packages installation

Benjamin Roth authored on25/05/2023 09:52:13
Showing1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,141 @@
1
+//==========================================================
2
+// head-support.js
3
+//
4
+// An extension to htmx 1.0 to add head tag merging.
5
+//==========================================================
6
+(function(){
7
+
8
+    var api = null;
9
+
10
+    function log() {
11
+        //console.log(arguments);
12
+    }
13
+
14
+    function mergeHead(newContent, defaultMergeStrategy) {
15
+
16
+        if (newContent && newContent.indexOf('<head') > -1) {
17
+            const htmlDoc = document.createElement("html");
18
+            // remove svgs to avoid conflicts
19
+            var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
20
+            // extract head tag
21
+            var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
22
+
23
+            // if the  head tag exists...
24
+            if (headTag) {
25
+
26
+                var added = []
27
+                var removed = []
28
+                var preserved = []
29
+                var nodesToAppend = []
30
+
31
+                htmlDoc.innerHTML = headTag;
32
+                var newHeadTag = htmlDoc.querySelector("head");
33
+                var currentHead = document.head;
34
+
35
+                if (newHeadTag == null) {
36
+                    return;
37
+                } else {
38
+                    // put all new head elements into a Map, by their outerHTML
39
+                    var srcToNewHeadNodes = new Map();
40
+                    for (const newHeadChild of newHeadTag.children) {
41
+                        srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
42
+                    }
43
+                }
44
+
45
+
46
+
47
+                // determine merge strategy
48
+                var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
49
+
50
+                // get the current head
51
+                for (const currentHeadElt of currentHead.children) {
52
+
53
+                    // If the current head element is in the map
54
+                    var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
55
+                    var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
56
+                    var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
57
+                    if (inNewContent || isPreserved) {
58
+                        if (isReAppended) {
59
+                            // remove the current version and let the new version replace it and re-execute
60
+                            removed.push(currentHeadElt);
61
+                        } else {
62
+                            // this element already exists and should not be re-appended, so remove it from
63
+                            // the new content map, preserving it in the DOM
64
+                            srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
65
+                            preserved.push(currentHeadElt);
66
+                        }
67
+                    } else {
68
+                        if (mergeStrategy === "append") {
69
+                            // we are appending and this existing element is not new content
70
+                            // so if and only if it is marked for re-append do we do anything
71
+                            if (isReAppended) {
72
+                                removed.push(currentHeadElt);
73
+                                nodesToAppend.push(currentHeadElt);
74
+                            }
75
+                        } else {
76
+                            // if this is a merge, we remove this content since it is not in the new head
77
+                            if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
78
+                                removed.push(currentHeadElt);
79
+                            }
80
+                        }
81
+                    }
82
+                }
83
+
84
+                // Push the tremaining new head elements in the Map into the
85
+                // nodes to append to the head tag
86
+                nodesToAppend.push(...srcToNewHeadNodes.values());
87
+                log("to append: ", nodesToAppend);
88
+
89
+                for (const newNode of nodesToAppend) {
90
+                    log("adding: ", newNode);
91
+                    var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
92
+                    log(newElt);
93
+                    if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
94
+                        currentHead.appendChild(newElt);
95
+                        added.push(newElt);
96
+                    }
97
+                }
98
+
99
+                // remove all removed elements, after we have appended the new elements to avoid
100
+                // additional network requests for things like style sheets
101
+                for (const removedElement of removed) {
102
+                    if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
103
+                        currentHead.removeChild(removedElement);
104
+                    }
105
+                }
106
+
107
+                api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
108
+            }
109
+        }
110
+    }
111
+
112
+    htmx.defineExtension("head-support", {
113
+        init: function(apiRef) {
114
+            // store a reference to the internal API.
115
+            api = apiRef;
116
+
117
+            htmx.on('htmx:afterSwap', function(evt){
118
+                var serverResponse = evt.detail.xhr.response;
119
+                if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
120
+                    mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
121
+                }
122
+            })
123
+
124
+            htmx.on('htmx:historyRestore', function(evt){
125
+                if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
126
+                    if (evt.detail.cacheMiss) {
127
+                        mergeHead(evt.detail.serverResponse, "merge");
128
+                    } else {
129
+                        mergeHead(evt.detail.item.head, "merge");
130
+                    }
131
+                }
132
+            })
133
+
134
+            htmx.on('htmx:historyItemCreated', function(evt){
135
+                var historyItem = evt.detail.item;
136
+                historyItem.head = document.head.outerHTML;
137
+            })
138
+        }
139
+    });
140
+
141
+})()
0 142
\ No newline at end of file