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