UNPKG

140 kBJavaScriptView Raw
1// UMD insanity
2// This code sets up support for (in order) AMD, ES6 modules, and globals.
3(function (root, factory) {
4 //@ts-ignore
5 if (typeof define === 'function' && define.amd) {
6 // AMD. Register as an anonymous module.
7 //@ts-ignore
8 define([], factory);
9 } else if (typeof module === 'object' && module.exports) {
10 // Node. Does not work with strict CommonJS, but
11 // only CommonJS-like environments that support module.exports,
12 // like Node.
13 module.exports = factory();
14 } else {
15 // Browser globals
16 root.htmx = root.htmx || factory();
17 }
18}(typeof self !== 'undefined' ? self : this, function () {
19return (function () {
20 'use strict';
21
22 // Public API
23 //** @type {import("./htmx").HtmxApi} */
24 // TODO: list all methods in public API
25 var htmx = {
26 onLoad: onLoadHelper,
27 process: processNode,
28 on: addEventListenerImpl,
29 off: removeEventListenerImpl,
30 trigger : triggerEvent,
31 ajax : ajaxHelper,
32 find : find,
33 findAll : findAll,
34 closest : closest,
35 values : function(elt, type){
36 var inputValues = getInputValues(elt, type || "post");
37 return inputValues.values;
38 },
39 remove : removeElement,
40 addClass : addClassToElement,
41 removeClass : removeClassFromElement,
42 toggleClass : toggleClassOnElement,
43 takeClass : takeClassForElement,
44 defineExtension : defineExtension,
45 removeExtension : removeExtension,
46 logAll : logAll,
47 logger : null,
48 config : {
49 historyEnabled:true,
50 historyCacheSize:10,
51 refreshOnHistoryMiss:false,
52 defaultSwapStyle:'innerHTML',
53 defaultSwapDelay:0,
54 defaultSettleDelay:20,
55 includeIndicatorStyles:true,
56 indicatorClass:'htmx-indicator',
57 requestClass:'htmx-request',
58 addedClass:'htmx-added',
59 settlingClass:'htmx-settling',
60 swappingClass:'htmx-swapping',
61 allowEval:true,
62 inlineScriptNonce:'',
63 attributesToSettle:["class", "style", "width", "height"],
64 withCredentials:false,
65 timeout:0,
66 wsReconnectDelay: 'full-jitter',
67 wsBinaryType: 'blob',
68 disableSelector: "[hx-disable], [data-hx-disable]",
69 useTemplateFragments: false,
70 scrollBehavior: 'smooth',
71 defaultFocusScroll: false,
72 getCacheBusterParam: false,
73 },
74 parseInterval:parseInterval,
75 _:internalEval,
76 createEventSource: function(url){
77 return new EventSource(url, {withCredentials:true})
78 },
79 createWebSocket: function(url){
80 var sock = new WebSocket(url, []);
81 sock.binaryType = htmx.config.wsBinaryType;
82 return sock;
83 },
84 version: "1.8.6"
85 };
86
87 /** @type {import("./htmx").HtmxInternalApi} */
88 var internalAPI = {
89 addTriggerHandler: addTriggerHandler,
90 bodyContains: bodyContains,
91 canAccessLocalStorage: canAccessLocalStorage,
92 filterValues: filterValues,
93 hasAttribute: hasAttribute,
94 getAttributeValue: getAttributeValue,
95 getClosestMatch: getClosestMatch,
96 getExpressionVars: getExpressionVars,
97 getHeaders: getHeaders,
98 getInputValues: getInputValues,
99 getInternalData: getInternalData,
100 getSwapSpecification: getSwapSpecification,
101 getTriggerSpecs: getTriggerSpecs,
102 getTarget: getTarget,
103 makeFragment: makeFragment,
104 mergeObjects: mergeObjects,
105 makeSettleInfo: makeSettleInfo,
106 oobSwap: oobSwap,
107 selectAndSwap: selectAndSwap,
108 settleImmediately: settleImmediately,
109 shouldCancel: shouldCancel,
110 triggerEvent: triggerEvent,
111 triggerErrorEvent: triggerErrorEvent,
112 withExtensions: withExtensions,
113 }
114
115 var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
116 var VERB_SELECTOR = VERBS.map(function(verb){
117 return "[hx-" + verb + "], [data-hx-" + verb + "]"
118 }).join(", ");
119
120 //====================================================================
121 // Utilities
122 //====================================================================
123
124 function parseInterval(str) {
125 if (str == undefined) {
126 return undefined
127 }
128 if (str.slice(-2) == "ms") {
129 return parseFloat(str.slice(0,-2)) || undefined
130 }
131 if (str.slice(-1) == "s") {
132 return (parseFloat(str.slice(0,-1)) * 1000) || undefined
133 }
134 if (str.slice(-1) == "m") {
135 return (parseFloat(str.slice(0,-1)) * 1000 * 60) || undefined
136 }
137 return parseFloat(str) || undefined
138 }
139
140 /**
141 * @param {HTMLElement} elt
142 * @param {string} name
143 * @returns {(string | null)}
144 */
145 function getRawAttribute(elt, name) {
146 return elt.getAttribute && elt.getAttribute(name);
147 }
148
149 // resolve with both hx and data-hx prefixes
150 function hasAttribute(elt, qualifiedName) {
151 return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
152 elt.hasAttribute("data-" + qualifiedName));
153 }
154
155 /**
156 *
157 * @param {HTMLElement} elt
158 * @param {string} qualifiedName
159 * @returns {(string | null)}
160 */
161 function getAttributeValue(elt, qualifiedName) {
162 return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName);
163 }
164
165 /**
166 * @param {HTMLElement} elt
167 * @returns {HTMLElement | null}
168 */
169 function parentElt(elt) {
170 return elt.parentElement;
171 }
172
173 /**
174 * @returns {Document}
175 */
176 function getDocument() {
177 return document;
178 }
179
180 /**
181 * @param {HTMLElement} elt
182 * @param {(e:HTMLElement) => boolean} condition
183 * @returns {HTMLElement | null}
184 */
185 function getClosestMatch(elt, condition) {
186 while (elt && !condition(elt)) {
187 elt = parentElt(elt);
188 }
189
190 return elt ? elt : null;
191 }
192
193 function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName){
194 var attributeValue = getAttributeValue(ancestor, attributeName);
195 var disinherit = getAttributeValue(ancestor, "hx-disinherit");
196 if (initialElement !== ancestor && disinherit && (disinherit === "*" || disinherit.split(" ").indexOf(attributeName) >= 0)) {
197 return "unset";
198 } else {
199 return attributeValue
200 }
201 }
202
203 /**
204 * @param {HTMLElement} elt
205 * @param {string} attributeName
206 * @returns {string | null}
207 */
208 function getClosestAttributeValue(elt, attributeName) {
209 var closestAttr = null;
210 getClosestMatch(elt, function (e) {
211 return closestAttr = getAttributeValueWithDisinheritance(elt, e, attributeName);
212 });
213 if (closestAttr !== "unset") {
214 return closestAttr;
215 }
216 }
217
218 /**
219 * @param {HTMLElement} elt
220 * @param {string} selector
221 * @returns {boolean}
222 */
223 function matches(elt, selector) {
224 // @ts-ignore: non-standard properties for browser compatability
225 // noinspection JSUnresolvedVariable
226 var matchesFunction = elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector;
227 return matchesFunction && matchesFunction.call(elt, selector);
228 }
229
230 /**
231 * @param {string} str
232 * @returns {string}
233 */
234 function getStartTag(str) {
235 var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
236 var match = tagMatcher.exec( str );
237 if (match) {
238 return match[1].toLowerCase();
239 } else {
240 return "";
241 }
242 }
243
244 /**
245 *
246 * @param {string} resp
247 * @param {number} depth
248 * @returns {Element}
249 */
250 function parseHTML(resp, depth) {
251 var parser = new DOMParser();
252 var responseDoc = parser.parseFromString(resp, "text/html");
253
254 /** @type {Element} */
255 var responseNode = responseDoc.body;
256 while (depth > 0) {
257 depth--;
258 // @ts-ignore
259 responseNode = responseNode.firstChild;
260 }
261 if (responseNode == null) {
262 // @ts-ignore
263 responseNode = getDocument().createDocumentFragment();
264 }
265 return responseNode;
266 }
267
268 /**
269 *
270 * @param {string} resp
271 * @returns {Element}
272 */
273 function makeFragment(resp) {
274 if (htmx.config.useTemplateFragments) {
275 var documentFragment = parseHTML("<body><template>" + resp + "</template></body>", 0);
276 // @ts-ignore type mismatch between DocumentFragment and Element.
277 // TODO: Are these close enough for htmx to use interchangably?
278 return documentFragment.querySelector('template').content;
279 } else {
280 var startTag = getStartTag(resp);
281 switch (startTag) {
282 case "thead":
283 case "tbody":
284 case "tfoot":
285 case "colgroup":
286 case "caption":
287 return parseHTML("<table>" + resp + "</table>", 1);
288 case "col":
289 return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
290 case "tr":
291 return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
292 case "td":
293 case "th":
294 return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
295 case "script":
296 return parseHTML("<div>" + resp + "</div>", 1);
297 default:
298 return parseHTML(resp, 0);
299 }
300 }
301 }
302
303 /**
304 * @param {Function} func
305 */
306 function maybeCall(func){
307 if(func) {
308 func();
309 }
310 }
311
312 /**
313 * @param {any} o
314 * @param {string} type
315 * @returns
316 */
317 function isType(o, type) {
318 return Object.prototype.toString.call(o) === "[object " + type + "]";
319 }
320
321 /**
322 * @param {*} o
323 * @returns {o is Function}
324 */
325 function isFunction(o) {
326 return isType(o, "Function");
327 }
328
329 /**
330 * @param {*} o
331 * @returns {o is Object}
332 */
333 function isRawObject(o) {
334 return isType(o, "Object");
335 }
336
337 /**
338 * getInternalData retrieves "private" data stored by htmx within an element
339 * @param {HTMLElement} elt
340 * @returns {*}
341 */
342 function getInternalData(elt) {
343 var dataProp = 'htmx-internal-data';
344 var data = elt[dataProp];
345 if (!data) {
346 data = elt[dataProp] = {};
347 }
348 return data;
349 }
350
351 /**
352 * toArray converts an ArrayLike object into a real array.
353 * @param {ArrayLike} arr
354 * @returns {any[]}
355 */
356 function toArray(arr) {
357 var returnArr = [];
358 if (arr) {
359 for (var i = 0; i < arr.length; i++) {
360 returnArr.push(arr[i]);
361 }
362 }
363 return returnArr
364 }
365
366 function forEach(arr, func) {
367 if (arr) {
368 for (var i = 0; i < arr.length; i++) {
369 func(arr[i]);
370 }
371 }
372 }
373
374 function isScrolledIntoView(el) {
375 var rect = el.getBoundingClientRect();
376 var elemTop = rect.top;
377 var elemBottom = rect.bottom;
378 return elemTop < window.innerHeight && elemBottom >= 0;
379 }
380
381 function bodyContains(elt) {
382 // IE Fix
383 if (elt.getRootNode && elt.getRootNode() instanceof ShadowRoot) {
384 return getDocument().body.contains(elt.getRootNode().host);
385 } else {
386 return getDocument().body.contains(elt);
387 }
388 }
389
390 function splitOnWhitespace(trigger) {
391 return trigger.trim().split(/\s+/);
392 }
393
394 /**
395 * mergeObjects takes all of the keys from
396 * obj2 and duplicates them into obj1
397 * @param {Object} obj1
398 * @param {Object} obj2
399 * @returns {Object}
400 */
401 function mergeObjects(obj1, obj2) {
402 for (var key in obj2) {
403 if (obj2.hasOwnProperty(key)) {
404 obj1[key] = obj2[key];
405 }
406 }
407 return obj1;
408 }
409
410 function parseJSON(jString) {
411 try {
412 return JSON.parse(jString);
413 } catch(error) {
414 logError(error);
415 return null;
416 }
417 }
418
419 function canAccessLocalStorage() {
420 var test = 'htmx:localStorageTest';
421 try {
422 localStorage.setItem(test, test);
423 localStorage.removeItem(test);
424 return true;
425 } catch(e) {
426 return false;
427 }
428 }
429
430 //==========================================================================================
431 // public API
432 //==========================================================================================
433
434 function internalEval(str){
435 return maybeEval(getDocument().body, function () {
436 return eval(str);
437 });
438 }
439
440 function onLoadHelper(callback) {
441 var value = htmx.on("htmx:load", function(evt) {
442 callback(evt.detail.elt);
443 });
444 return value;
445 }
446
447 function logAll(){
448 htmx.logger = function(elt, event, data) {
449 if(console) {
450 console.log(event, elt, data);
451 }
452 }
453 }
454
455 function find(eltOrSelector, selector) {
456 if (selector) {
457 return eltOrSelector.querySelector(selector);
458 } else {
459 return find(getDocument(), eltOrSelector);
460 }
461 }
462
463 function findAll(eltOrSelector, selector) {
464 if (selector) {
465 return eltOrSelector.querySelectorAll(selector);
466 } else {
467 return findAll(getDocument(), eltOrSelector);
468 }
469 }
470
471 function removeElement(elt, delay) {
472 elt = resolveTarget(elt);
473 if (delay) {
474 setTimeout(function(){removeElement(elt);}, delay)
475 } else {
476 elt.parentElement.removeChild(elt);
477 }
478 }
479
480 function addClassToElement(elt, clazz, delay) {
481 elt = resolveTarget(elt);
482 if (delay) {
483 setTimeout(function(){addClassToElement(elt, clazz);}, delay)
484 } else {
485 elt.classList && elt.classList.add(clazz);
486 }
487 }
488
489 function removeClassFromElement(elt, clazz, delay) {
490 elt = resolveTarget(elt);
491 if (delay) {
492 setTimeout(function(){removeClassFromElement(elt, clazz);}, delay)
493 } else {
494 if (elt.classList) {
495 elt.classList.remove(clazz);
496 // if there are no classes left, remove the class attribute
497 if (elt.classList.length === 0) {
498 elt.removeAttribute("class");
499 }
500 }
501 }
502 }
503
504 function toggleClassOnElement(elt, clazz) {
505 elt = resolveTarget(elt);
506 elt.classList.toggle(clazz);
507 }
508
509 function takeClassForElement(elt, clazz) {
510 elt = resolveTarget(elt);
511 forEach(elt.parentElement.children, function(child){
512 removeClassFromElement(child, clazz);
513 })
514 addClassToElement(elt, clazz);
515 }
516
517 function closest(elt, selector) {
518 elt = resolveTarget(elt);
519 if (elt.closest) {
520 return elt.closest(selector);
521 } else {
522 // TODO remove when IE goes away
523 do{
524 if (elt == null || matches(elt, selector)){
525 return elt;
526 }
527 }
528 while (elt = elt && parentElt(elt));
529 return null;
530 }
531 }
532
533 function querySelectorAllExt(elt, selector) {
534 if (selector.indexOf("closest ") === 0) {
535 return [closest(elt, selector.substr(8))];
536 } else if (selector.indexOf("find ") === 0) {
537 return [find(elt, selector.substr(5))];
538 } else if (selector.indexOf("next ") === 0) {
539 return [scanForwardQuery(elt, selector.substr(5))];
540 } else if (selector.indexOf("previous ") === 0) {
541 return [scanBackwardsQuery(elt, selector.substr(9))];
542 } else if (selector === 'document') {
543 return [document];
544 } else if (selector === 'window') {
545 return [window];
546 } else {
547 return getDocument().querySelectorAll(selector);
548 }
549 }
550
551 var scanForwardQuery = function(start, match) {
552 var results = getDocument().querySelectorAll(match);
553 for (var i = 0; i < results.length; i++) {
554 var elt = results[i];
555 if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) {
556 return elt;
557 }
558 }
559 }
560
561 var scanBackwardsQuery = function(start, match) {
562 var results = getDocument().querySelectorAll(match);
563 for (var i = results.length - 1; i >= 0; i--) {
564 var elt = results[i];
565 if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) {
566 return elt;
567 }
568 }
569 }
570
571 function querySelectorExt(eltOrSelector, selector) {
572 if (selector) {
573 return querySelectorAllExt(eltOrSelector, selector)[0];
574 } else {
575 return querySelectorAllExt(getDocument().body, eltOrSelector)[0];
576 }
577 }
578
579 function resolveTarget(arg2) {
580 if (isType(arg2, 'String')) {
581 return find(arg2);
582 } else {
583 return arg2;
584 }
585 }
586
587 function processEventArgs(arg1, arg2, arg3) {
588 if (isFunction(arg2)) {
589 return {
590 target: getDocument().body,
591 event: arg1,
592 listener: arg2
593 }
594 } else {
595 return {
596 target: resolveTarget(arg1),
597 event: arg2,
598 listener: arg3
599 }
600 }
601
602 }
603
604 function addEventListenerImpl(arg1, arg2, arg3) {
605 ready(function(){
606 var eventArgs = processEventArgs(arg1, arg2, arg3);
607 eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener);
608 })
609 var b = isFunction(arg2);
610 return b ? arg2 : arg3;
611 }
612
613 function removeEventListenerImpl(arg1, arg2, arg3) {
614 ready(function(){
615 var eventArgs = processEventArgs(arg1, arg2, arg3);
616 eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener);
617 })
618 return isFunction(arg2) ? arg2 : arg3;
619 }
620
621 //====================================================================
622 // Node processing
623 //====================================================================
624
625 var DUMMY_ELT = getDocument().createElement("output"); // dummy element for bad selectors
626 function findAttributeTargets(elt, attrName) {
627 var attrTarget = getClosestAttributeValue(elt, attrName);
628 if (attrTarget) {
629 if (attrTarget === "this") {
630 return [findThisElement(elt, attrName)];
631 } else {
632 var result = querySelectorAllExt(elt, attrTarget);
633 if (result.length === 0) {
634 logError('The selector "' + attrTarget + '" on ' + attrName + " returned no matches!");
635 return [DUMMY_ELT]
636 } else {
637 return result;
638 }
639 }
640 }
641 }
642
643 function findThisElement(elt, attribute){
644 return getClosestMatch(elt, function (elt) {
645 return getAttributeValue(elt, attribute) != null;
646 })
647 }
648
649 function getTarget(elt) {
650 var targetStr = getClosestAttributeValue(elt, "hx-target");
651 if (targetStr) {
652 if (targetStr === "this") {
653 return findThisElement(elt,'hx-target');
654 } else {
655 return querySelectorExt(elt, targetStr)
656 }
657 } else {
658 var data = getInternalData(elt);
659 if (data.boosted) {
660 return getDocument().body;
661 } else {
662 return elt;
663 }
664 }
665 }
666
667 function shouldSettleAttribute(name) {
668 var attributesToSettle = htmx.config.attributesToSettle;
669 for (var i = 0; i < attributesToSettle.length; i++) {
670 if (name === attributesToSettle[i]) {
671 return true;
672 }
673 }
674 return false;
675 }
676
677 function cloneAttributes(mergeTo, mergeFrom) {
678 forEach(mergeTo.attributes, function (attr) {
679 if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) {
680 mergeTo.removeAttribute(attr.name)
681 }
682 });
683 forEach(mergeFrom.attributes, function (attr) {
684 if (shouldSettleAttribute(attr.name)) {
685 mergeTo.setAttribute(attr.name, attr.value);
686 }
687 });
688 }
689
690 function isInlineSwap(swapStyle, target) {
691 var extensions = getExtensions(target);
692 for (var i = 0; i < extensions.length; i++) {
693 var extension = extensions[i];
694 try {
695 if (extension.isInlineSwap(swapStyle)) {
696 return true;
697 }
698 } catch(e) {
699 logError(e);
700 }
701 }
702 return swapStyle === "outerHTML";
703 }
704
705 /**
706 *
707 * @param {string} oobValue
708 * @param {HTMLElement} oobElement
709 * @param {*} settleInfo
710 * @returns
711 */
712 function oobSwap(oobValue, oobElement, settleInfo) {
713 var selector = "#" + oobElement.id;
714 var swapStyle = "outerHTML";
715 if (oobValue === "true") {
716 // do nothing
717 } else if (oobValue.indexOf(":") > 0) {
718 swapStyle = oobValue.substr(0, oobValue.indexOf(":"));
719 selector = oobValue.substr(oobValue.indexOf(":") + 1, oobValue.length);
720 } else {
721 swapStyle = oobValue;
722 }
723
724 var targets = getDocument().querySelectorAll(selector);
725 if (targets) {
726 forEach(
727 targets,
728 function (target) {
729 var fragment;
730 var oobElementClone = oobElement.cloneNode(true);
731 fragment = getDocument().createDocumentFragment();
732 fragment.appendChild(oobElementClone);
733 if (!isInlineSwap(swapStyle, target)) {
734 fragment = oobElementClone; // if this is not an inline swap, we use the content of the node, not the node itself
735 }
736
737 var beforeSwapDetails = {shouldSwap: true, target: target, fragment:fragment };
738 if (!triggerEvent(target, 'htmx:oobBeforeSwap', beforeSwapDetails)) return;
739
740 target = beforeSwapDetails.target; // allow re-targeting
741 if (beforeSwapDetails['shouldSwap']){
742 swap(swapStyle, target, target, fragment, settleInfo);
743 }
744 forEach(settleInfo.elts, function (elt) {
745 triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails);
746 });
747 }
748 );
749 oobElement.parentNode.removeChild(oobElement);
750 } else {
751 oobElement.parentNode.removeChild(oobElement);
752 triggerErrorEvent(getDocument().body, "htmx:oobErrorNoTarget", {content: oobElement});
753 }
754 return oobValue;
755 }
756
757 function handleOutOfBandSwaps(elt, fragment, settleInfo) {
758 var oobSelects = getClosestAttributeValue(elt, "hx-select-oob");
759 if (oobSelects) {
760 var oobSelectValues = oobSelects.split(",");
761 for (let i = 0; i < oobSelectValues.length; i++) {
762 var oobSelectValue = oobSelectValues[i].split(":", 2);
763 var id = oobSelectValue[0];
764 if (id.indexOf("#") === 0) {
765 id = id.substring(1);
766 }
767 var oobValue = oobSelectValue[1] || "true";
768 var oobElement = fragment.querySelector("#" + id);
769 if (oobElement) {
770 oobSwap(oobValue, oobElement, settleInfo);
771 }
772 }
773 }
774 forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) {
775 var oobValue = getAttributeValue(oobElement, "hx-swap-oob");
776 if (oobValue != null) {
777 oobSwap(oobValue, oobElement, settleInfo);
778 }
779 });
780 }
781
782 function handlePreservedElements(fragment) {
783 forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) {
784 var id = getAttributeValue(preservedElt, "id");
785 var oldElt = getDocument().getElementById(id);
786 if (oldElt != null) {
787 preservedElt.parentNode.replaceChild(oldElt, preservedElt);
788 }
789 });
790 }
791
792 function handleAttributes(parentNode, fragment, settleInfo) {
793 forEach(fragment.querySelectorAll("[id]"), function (newNode) {
794 if (newNode.id && newNode.id.length > 0) {
795 var normalizedId = newNode.id.replace("'", "\\'");
796 var oldNode = parentNode.querySelector(newNode.tagName + "[id='" + normalizedId + "']");
797 if (oldNode && oldNode !== parentNode) {
798 var newAttributes = newNode.cloneNode();
799 cloneAttributes(newNode, oldNode);
800 settleInfo.tasks.push(function () {
801 cloneAttributes(newNode, newAttributes);
802 });
803 }
804 }
805 });
806 }
807
808 function makeAjaxLoadTask(child) {
809 return function () {
810 removeClassFromElement(child, htmx.config.addedClass);
811 processNode(child);
812 processScripts(child);
813 processFocus(child)
814 triggerEvent(child, 'htmx:load');
815 };
816 }
817
818 function processFocus(child) {
819 var autofocus = "[autofocus]";
820 var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus)
821 if (autoFocusedElt != null) {
822 autoFocusedElt.focus();
823 }
824 }
825
826 function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) {
827 handleAttributes(parentNode, fragment, settleInfo);
828 while(fragment.childNodes.length > 0){
829 var child = fragment.firstChild;
830 addClassToElement(child, htmx.config.addedClass);
831 parentNode.insertBefore(child, insertBefore);
832 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
833 settleInfo.tasks.push(makeAjaxLoadTask(child));
834 }
835 }
836 }
837
838 // based on https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0,
839 // derived from Java's string hashcode implementation
840 function stringHash(string, hash) {
841 var char = 0;
842 while (char < string.length){
843 hash = (hash << 5) - hash + string.charCodeAt(char++) | 0; // bitwise or ensures we have a 32-bit int
844 }
845 return hash;
846 }
847
848 function attributeHash(elt) {
849 var hash = 0;
850 // IE fix
851 if (elt.attributes) {
852 for (var i = 0; i < elt.attributes.length; i++) {
853 var attribute = elt.attributes[i];
854 if(attribute.value){ // only include attributes w/ actual values (empty is same as non-existent)
855 hash = stringHash(attribute.name, hash);
856 hash = stringHash(attribute.value, hash);
857 }
858 }
859 }
860 return hash;
861 }
862
863 function deInitNode(element) {
864 var internalData = getInternalData(element);
865 if (internalData.webSocket) {
866 internalData.webSocket.close();
867 }
868 if (internalData.sseEventSource) {
869 internalData.sseEventSource.close();
870 }
871 if (internalData.listenerInfos) {
872 forEach(internalData.listenerInfos, function (info) {
873 if (info.on) {
874 info.on.removeEventListener(info.trigger, info.listener);
875 }
876 });
877 }
878 }
879
880 function cleanUpElement(element) {
881 triggerEvent(element, "htmx:beforeCleanupElement")
882 deInitNode(element);
883 if (element.children) { // IE
884 forEach(element.children, function(child) { cleanUpElement(child) });
885 }
886 }
887
888 function swapOuterHTML(target, fragment, settleInfo) {
889 if (target.tagName === "BODY") {
890 return swapInnerHTML(target, fragment, settleInfo);
891 } else {
892 // @type {HTMLElement}
893 var newElt
894 var eltBeforeNewContent = target.previousSibling;
895 insertNodesBefore(parentElt(target), target, fragment, settleInfo);
896 if (eltBeforeNewContent == null) {
897 newElt = parentElt(target).firstChild;
898 } else {
899 newElt = eltBeforeNewContent.nextSibling;
900 }
901 getInternalData(target).replacedWith = newElt; // tuck away so we can fire events on it later
902 settleInfo.elts = [] // clear existing elements
903 while(newElt && newElt !== target) {
904 if (newElt.nodeType === Node.ELEMENT_NODE) {
905 settleInfo.elts.push(newElt);
906 }
907 newElt = newElt.nextElementSibling;
908 }
909 cleanUpElement(target);
910 parentElt(target).removeChild(target);
911 }
912 }
913
914 function swapAfterBegin(target, fragment, settleInfo) {
915 return insertNodesBefore(target, target.firstChild, fragment, settleInfo);
916 }
917
918 function swapBeforeBegin(target, fragment, settleInfo) {
919 return insertNodesBefore(parentElt(target), target, fragment, settleInfo);
920 }
921
922 function swapBeforeEnd(target, fragment, settleInfo) {
923 return insertNodesBefore(target, null, fragment, settleInfo);
924 }
925
926 function swapAfterEnd(target, fragment, settleInfo) {
927 return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo);
928 }
929 function swapDelete(target, fragment, settleInfo) {
930 cleanUpElement(target);
931 return parentElt(target).removeChild(target);
932 }
933
934 function swapInnerHTML(target, fragment, settleInfo) {
935 var firstChild = target.firstChild;
936 insertNodesBefore(target, firstChild, fragment, settleInfo);
937 if (firstChild) {
938 while (firstChild.nextSibling) {
939 cleanUpElement(firstChild.nextSibling)
940 target.removeChild(firstChild.nextSibling);
941 }
942 cleanUpElement(firstChild)
943 target.removeChild(firstChild);
944 }
945 }
946
947 function maybeSelectFromResponse(elt, fragment) {
948 var selector = getClosestAttributeValue(elt, "hx-select");
949 if (selector) {
950 var newFragment = getDocument().createDocumentFragment();
951 forEach(fragment.querySelectorAll(selector), function (node) {
952 newFragment.appendChild(node);
953 });
954 fragment = newFragment;
955 }
956 return fragment;
957 }
958
959 function swap(swapStyle, elt, target, fragment, settleInfo) {
960 switch (swapStyle) {
961 case "none":
962 return;
963 case "outerHTML":
964 swapOuterHTML(target, fragment, settleInfo);
965 return;
966 case "afterbegin":
967 swapAfterBegin(target, fragment, settleInfo);
968 return;
969 case "beforebegin":
970 swapBeforeBegin(target, fragment, settleInfo);
971 return;
972 case "beforeend":
973 swapBeforeEnd(target, fragment, settleInfo);
974 return;
975 case "afterend":
976 swapAfterEnd(target, fragment, settleInfo);
977 return;
978 case "delete":
979 swapDelete(target, fragment, settleInfo);
980 return;
981 default:
982 var extensions = getExtensions(elt);
983 for (var i = 0; i < extensions.length; i++) {
984 var ext = extensions[i];
985 try {
986 var newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo);
987 if (newElements) {
988 if (typeof newElements.length !== 'undefined') {
989 // if handleSwap returns an array (like) of elements, we handle them
990 for (var j = 0; j < newElements.length; j++) {
991 var child = newElements[j];
992 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
993 settleInfo.tasks.push(makeAjaxLoadTask(child));
994 }
995 }
996 }
997 return;
998 }
999 } catch (e) {
1000 logError(e);
1001 }
1002 }
1003 if (swapStyle === "innerHTML") {
1004 swapInnerHTML(target, fragment, settleInfo);
1005 } else {
1006 swap(htmx.config.defaultSwapStyle, elt, target, fragment, settleInfo);
1007 }
1008 }
1009 }
1010
1011 function findTitle(content) {
1012 if (content.indexOf('<title') > -1) {
1013 var contentWithSvgsRemoved = content.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
1014 var result = contentWithSvgsRemoved.match(/<title(\s[^>]*>|>)([\s\S]*?)<\/title>/im);
1015
1016 if (result) {
1017 return result[2];
1018 }
1019 }
1020 }
1021
1022 function selectAndSwap(swapStyle, target, elt, responseText, settleInfo) {
1023 settleInfo.title = findTitle(responseText);
1024 var fragment = makeFragment(responseText);
1025 if (fragment) {
1026 handleOutOfBandSwaps(elt, fragment, settleInfo);
1027 fragment = maybeSelectFromResponse(elt, fragment);
1028 handlePreservedElements(fragment);
1029 return swap(swapStyle, elt, target, fragment, settleInfo);
1030 }
1031 }
1032
1033 function handleTrigger(xhr, header, elt) {
1034 var triggerBody = xhr.getResponseHeader(header);
1035 if (triggerBody.indexOf("{") === 0) {
1036 var triggers = parseJSON(triggerBody);
1037 for (var eventName in triggers) {
1038 if (triggers.hasOwnProperty(eventName)) {
1039 var detail = triggers[eventName];
1040 if (!isRawObject(detail)) {
1041 detail = {"value": detail}
1042 }
1043 triggerEvent(elt, eventName, detail);
1044 }
1045 }
1046 } else {
1047 triggerEvent(elt, triggerBody, []);
1048 }
1049 }
1050
1051 var WHITESPACE = /\s/;
1052 var WHITESPACE_OR_COMMA = /[\s,]/;
1053 var SYMBOL_START = /[_$a-zA-Z]/;
1054 var SYMBOL_CONT = /[_$a-zA-Z0-9]/;
1055 var STRINGISH_START = ['"', "'", "/"];
1056 var NOT_WHITESPACE = /[^\s]/;
1057 function tokenizeString(str) {
1058 var tokens = [];
1059 var position = 0;
1060 while (position < str.length) {
1061 if(SYMBOL_START.exec(str.charAt(position))) {
1062 var startPosition = position;
1063 while (SYMBOL_CONT.exec(str.charAt(position + 1))) {
1064 position++;
1065 }
1066 tokens.push(str.substr(startPosition, position - startPosition + 1));
1067 } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) {
1068 var startChar = str.charAt(position);
1069 var startPosition = position;
1070 position++;
1071 while (position < str.length && str.charAt(position) !== startChar ) {
1072 if (str.charAt(position) === "\\") {
1073 position++;
1074 }
1075 position++;
1076 }
1077 tokens.push(str.substr(startPosition, position - startPosition + 1));
1078 } else {
1079 var symbol = str.charAt(position);
1080 tokens.push(symbol);
1081 }
1082 position++;
1083 }
1084 return tokens;
1085 }
1086
1087 function isPossibleRelativeReference(token, last, paramName) {
1088 return SYMBOL_START.exec(token.charAt(0)) &&
1089 token !== "true" &&
1090 token !== "false" &&
1091 token !== "this" &&
1092 token !== paramName &&
1093 last !== ".";
1094 }
1095
1096 function maybeGenerateConditional(elt, tokens, paramName) {
1097 if (tokens[0] === '[') {
1098 tokens.shift();
1099 var bracketCount = 1;
1100 var conditionalSource = " return (function(" + paramName + "){ return (";
1101 var last = null;
1102 while (tokens.length > 0) {
1103 var token = tokens[0];
1104 if (token === "]") {
1105 bracketCount--;
1106 if (bracketCount === 0) {
1107 if (last === null) {
1108 conditionalSource = conditionalSource + "true";
1109 }
1110 tokens.shift();
1111 conditionalSource += ")})";
1112 try {
1113 var conditionFunction = maybeEval(elt,function () {
1114 return Function(conditionalSource)();
1115 },
1116 function(){return true})
1117 conditionFunction.source = conditionalSource;
1118 return conditionFunction;
1119 } catch (e) {
1120 triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource})
1121 return null;
1122 }
1123 }
1124 } else if (token === "[") {
1125 bracketCount++;
1126 }
1127 if (isPossibleRelativeReference(token, last, paramName)) {
1128 conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
1129 } else {
1130 conditionalSource = conditionalSource + token;
1131 }
1132 last = tokens.shift();
1133 }
1134 }
1135 }
1136
1137 function consumeUntil(tokens, match) {
1138 var result = "";
1139 while (tokens.length > 0 && !tokens[0].match(match)) {
1140 result += tokens.shift();
1141 }
1142 return result;
1143 }
1144
1145 var INPUT_SELECTOR = 'input, textarea, select';
1146
1147 /**
1148 * @param {HTMLElement} elt
1149 * @returns {import("./htmx").HtmxTriggerSpecification[]}
1150 */
1151 function getTriggerSpecs(elt) {
1152 var explicitTrigger = getAttributeValue(elt, 'hx-trigger');
1153 var triggerSpecs = [];
1154 if (explicitTrigger) {
1155 var tokens = tokenizeString(explicitTrigger);
1156 do {
1157 consumeUntil(tokens, NOT_WHITESPACE);
1158 var initialLength = tokens.length;
1159 var trigger = consumeUntil(tokens, /[,\[\s]/);
1160 if (trigger !== "") {
1161 if (trigger === "every") {
1162 var every = {trigger: 'every'};
1163 consumeUntil(tokens, NOT_WHITESPACE);
1164 every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/));
1165 consumeUntil(tokens, NOT_WHITESPACE);
1166 var eventFilter = maybeGenerateConditional(elt, tokens, "event");
1167 if (eventFilter) {
1168 every.eventFilter = eventFilter;
1169 }
1170 triggerSpecs.push(every);
1171 } else if (trigger.indexOf("sse:") === 0) {
1172 triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
1173 } else {
1174 var triggerSpec = {trigger: trigger};
1175 var eventFilter = maybeGenerateConditional(elt, tokens, "event");
1176 if (eventFilter) {
1177 triggerSpec.eventFilter = eventFilter;
1178 }
1179 while (tokens.length > 0 && tokens[0] !== ",") {
1180 consumeUntil(tokens, NOT_WHITESPACE)
1181 var token = tokens.shift();
1182 if (token === "changed") {
1183 triggerSpec.changed = true;
1184 } else if (token === "once") {
1185 triggerSpec.once = true;
1186 } else if (token === "consume") {
1187 triggerSpec.consume = true;
1188 } else if (token === "delay" && tokens[0] === ":") {
1189 tokens.shift();
1190 triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
1191 } else if (token === "from" && tokens[0] === ":") {
1192 tokens.shift();
1193 var from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1194 if (from_arg === "closest" || from_arg === "find" || from_arg === "next" || from_arg === "previous") {
1195 tokens.shift();
1196 from_arg +=
1197 " " +
1198 consumeUntil(
1199 tokens,
1200 WHITESPACE_OR_COMMA
1201 );
1202 }
1203 triggerSpec.from = from_arg;
1204 } else if (token === "target" && tokens[0] === ":") {
1205 tokens.shift();
1206 triggerSpec.target = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1207 } else if (token === "throttle" && tokens[0] === ":") {
1208 tokens.shift();
1209 triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
1210 } else if (token === "queue" && tokens[0] === ":") {
1211 tokens.shift();
1212 triggerSpec.queue = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1213 } else if ((token === "root" || token === "threshold") && tokens[0] === ":") {
1214 tokens.shift();
1215 triggerSpec[token] = consumeUntil(tokens, WHITESPACE_OR_COMMA);
1216 } else {
1217 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
1218 }
1219 }
1220 triggerSpecs.push(triggerSpec);
1221 }
1222 }
1223 if (tokens.length === initialLength) {
1224 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
1225 }
1226 consumeUntil(tokens, NOT_WHITESPACE);
1227 } while (tokens[0] === "," && tokens.shift())
1228 }
1229
1230 if (triggerSpecs.length > 0) {
1231 return triggerSpecs;
1232 } else if (matches(elt, 'form')) {
1233 return [{trigger: 'submit'}];
1234 } else if (matches(elt, 'input[type="button"]')){
1235 return [{trigger: 'click'}];
1236 } else if (matches(elt, INPUT_SELECTOR)) {
1237 return [{trigger: 'change'}];
1238 } else {
1239 return [{trigger: 'click'}];
1240 }
1241 }
1242
1243 function cancelPolling(elt) {
1244 getInternalData(elt).cancelled = true;
1245 }
1246
1247 function processPolling(elt, handler, spec) {
1248 var nodeData = getInternalData(elt);
1249 nodeData.timeout = setTimeout(function () {
1250 if (bodyContains(elt) && nodeData.cancelled !== true) {
1251 if (!maybeFilterEvent(spec, makeEvent('hx:poll:trigger', {triggerSpec:spec, target:elt}))) {
1252 handler(elt);
1253 }
1254 processPolling(elt, handler, spec);
1255 }
1256 }, spec.pollInterval);
1257 }
1258
1259 function isLocalLink(elt) {
1260 return location.hostname === elt.hostname &&
1261 getRawAttribute(elt,'href') &&
1262 getRawAttribute(elt,'href').indexOf("#") !== 0;
1263 }
1264
1265 function boostElement(elt, nodeData, triggerSpecs) {
1266 if ((elt.tagName === "A" && isLocalLink(elt) && (elt.target === "" || elt.target === "_self")) || elt.tagName === "FORM") {
1267 nodeData.boosted = true;
1268 var verb, path;
1269 if (elt.tagName === "A") {
1270 verb = "get";
1271 path = getRawAttribute(elt, 'href');
1272 } else {
1273 var rawAttribute = getRawAttribute(elt, "method");
1274 verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
1275 if (verb === "get") {
1276 }
1277 path = getRawAttribute(elt, 'action');
1278 }
1279 triggerSpecs.forEach(function(triggerSpec) {
1280 addEventListener(elt, function(elt, evt) {
1281 issueAjaxRequest(verb, path, elt, evt)
1282 }, nodeData, triggerSpec, true);
1283 });
1284 }
1285 }
1286
1287 /**
1288 *
1289 * @param {Event} evt
1290 * @param {HTMLElement} elt
1291 * @returns
1292 */
1293 function shouldCancel(evt, elt) {
1294 if (evt.type === "submit" || evt.type === "click") {
1295 if (elt.tagName === "FORM") {
1296 return true;
1297 }
1298 if (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) {
1299 return true;
1300 }
1301 if (elt.tagName === "A" && elt.href &&
1302 (elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf("#") !== 0)) {
1303 return true;
1304 }
1305 }
1306 return false;
1307 }
1308
1309 function ignoreBoostedAnchorCtrlClick(elt, evt) {
1310 return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && (evt.ctrlKey || evt.metaKey);
1311 }
1312
1313 function maybeFilterEvent(triggerSpec, evt) {
1314 var eventFilter = triggerSpec.eventFilter;
1315 if(eventFilter){
1316 try {
1317 return eventFilter(evt) !== true;
1318 } catch(e) {
1319 triggerErrorEvent(getDocument().body, "htmx:eventFilter:error", {error: e, source:eventFilter.source});
1320 return true;
1321 }
1322 }
1323 return false;
1324 }
1325
1326 function addEventListener(elt, handler, nodeData, triggerSpec, explicitCancel) {
1327 var elementData = getInternalData(elt);
1328 var eltsToListenOn;
1329 if (triggerSpec.from) {
1330 eltsToListenOn = querySelectorAllExt(elt, triggerSpec.from);
1331 } else {
1332 eltsToListenOn = [elt];
1333 }
1334 // store the initial value of the element so we can tell if it changes
1335 if (triggerSpec.changed) {
1336 elementData.lastValue = elt.value;
1337 }
1338 forEach(eltsToListenOn, function (eltToListenOn) {
1339 var eventListener = function (evt) {
1340 if (!bodyContains(elt)) {
1341 eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener);
1342 return;
1343 }
1344 if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
1345 return;
1346 }
1347 if (explicitCancel || shouldCancel(evt, elt)) {
1348 evt.preventDefault();
1349 }
1350 if (maybeFilterEvent(triggerSpec, evt)) {
1351 return;
1352 }
1353 var eventData = getInternalData(evt);
1354 eventData.triggerSpec = triggerSpec;
1355 if (eventData.handledFor == null) {
1356 eventData.handledFor = [];
1357 }
1358 if (eventData.handledFor.indexOf(elt) < 0) {
1359 eventData.handledFor.push(elt);
1360 if (triggerSpec.consume) {
1361 evt.stopPropagation();
1362 }
1363 if (triggerSpec.target && evt.target) {
1364 if (!matches(evt.target, triggerSpec.target)) {
1365 return;
1366 }
1367 }
1368 if (triggerSpec.once) {
1369 if (elementData.triggeredOnce) {
1370 return;
1371 } else {
1372 elementData.triggeredOnce = true;
1373 }
1374 }
1375 if (triggerSpec.changed) {
1376 if (elementData.lastValue === elt.value) {
1377 return;
1378 } else {
1379 elementData.lastValue = elt.value;
1380 }
1381 }
1382 if (elementData.delayed) {
1383 clearTimeout(elementData.delayed);
1384 }
1385 if (elementData.throttle) {
1386 return;
1387 }
1388
1389 if (triggerSpec.throttle) {
1390 if (!elementData.throttle) {
1391 handler(elt, evt);
1392 elementData.throttle = setTimeout(function () {
1393 elementData.throttle = null;
1394 }, triggerSpec.throttle);
1395 }
1396 } else if (triggerSpec.delay) {
1397 elementData.delayed = setTimeout(function() { handler(elt, evt) }, triggerSpec.delay);
1398 } else {
1399 handler(elt, evt);
1400 }
1401 }
1402 };
1403 if (nodeData.listenerInfos == null) {
1404 nodeData.listenerInfos = [];
1405 }
1406 nodeData.listenerInfos.push({
1407 trigger: triggerSpec.trigger,
1408 listener: eventListener,
1409 on: eltToListenOn
1410 })
1411 eltToListenOn.addEventListener(triggerSpec.trigger, eventListener);
1412 });
1413 }
1414
1415 var windowIsScrolling = false // used by initScrollHandler
1416 var scrollHandler = null;
1417 function initScrollHandler() {
1418 if (!scrollHandler) {
1419 scrollHandler = function() {
1420 windowIsScrolling = true
1421 };
1422 window.addEventListener("scroll", scrollHandler)
1423 setInterval(function() {
1424 if (windowIsScrolling) {
1425 windowIsScrolling = false;
1426 forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) {
1427 maybeReveal(elt);
1428 })
1429 }
1430 }, 200);
1431 }
1432 }
1433
1434 function maybeReveal(elt) {
1435 if (!hasAttribute(elt,'data-hx-revealed') && isScrolledIntoView(elt)) {
1436 elt.setAttribute('data-hx-revealed', 'true');
1437 var nodeData = getInternalData(elt);
1438 if (nodeData.initHash) {
1439 triggerEvent(elt, 'revealed');
1440 } else {
1441 // if the node isn't initialized, wait for it before triggering the request
1442 elt.addEventListener("htmx:afterProcessNode", function(evt) { triggerEvent(elt, 'revealed') }, {once: true});
1443 }
1444 }
1445 }
1446
1447 //====================================================================
1448 // Web Sockets
1449 //====================================================================
1450
1451 function processWebSocketInfo(elt, nodeData, info) {
1452 var values = splitOnWhitespace(info);
1453 for (var i = 0; i < values.length; i++) {
1454 var value = values[i].split(/:(.+)/);
1455 if (value[0] === "connect") {
1456 ensureWebSocket(elt, value[1], 0);
1457 }
1458 if (value[0] === "send") {
1459 processWebSocketSend(elt);
1460 }
1461 }
1462 }
1463
1464 function ensureWebSocket(elt, wssSource, retryCount) {
1465 if (!bodyContains(elt)) {
1466 return; // stop ensuring websocket connection when socket bearing element ceases to exist
1467 }
1468
1469 if (wssSource.indexOf("/") == 0) { // complete absolute paths only
1470 var base_part = location.hostname + (location.port ? ':'+location.port: '');
1471 if (location.protocol == 'https:') {
1472 wssSource = "wss://" + base_part + wssSource;
1473 } else if (location.protocol == 'http:') {
1474 wssSource = "ws://" + base_part + wssSource;
1475 }
1476 }
1477 var socket = htmx.createWebSocket(wssSource);
1478 socket.onerror = function (e) {
1479 triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket});
1480 maybeCloseWebSocketSource(elt);
1481 };
1482
1483 socket.onclose = function (e) {
1484 if ([1006, 1012, 1013].indexOf(e.code) >= 0) { // Abnormal Closure/Service Restart/Try Again Later
1485 var delay = getWebSocketReconnectDelay(retryCount);
1486 setTimeout(function() {
1487 ensureWebSocket(elt, wssSource, retryCount+1); // creates a websocket with a new timeout
1488 }, delay);
1489 }
1490 };
1491 socket.onopen = function (e) {
1492 retryCount = 0;
1493 }
1494
1495 getInternalData(elt).webSocket = socket;
1496 socket.addEventListener('message', function (event) {
1497 if (maybeCloseWebSocketSource(elt)) {
1498 return;
1499 }
1500
1501 var response = event.data;
1502 withExtensions(elt, function(extension){
1503 response = extension.transformResponse(response, null, elt);
1504 });
1505
1506 var settleInfo = makeSettleInfo(elt);
1507 var fragment = makeFragment(response);
1508 var children = toArray(fragment.children);
1509 for (var i = 0; i < children.length; i++) {
1510 var child = children[i];
1511 oobSwap(getAttributeValue(child, "hx-swap-oob") || "true", child, settleInfo);
1512 }
1513
1514 settleImmediately(settleInfo.tasks);
1515 });
1516 }
1517
1518 function maybeCloseWebSocketSource(elt) {
1519 if (!bodyContains(elt)) {
1520 getInternalData(elt).webSocket.close();
1521 return true;
1522 }
1523 }
1524
1525 function processWebSocketSend(elt) {
1526 var webSocketSourceElt = getClosestMatch(elt, function (parent) {
1527 return getInternalData(parent).webSocket != null;
1528 });
1529 if (webSocketSourceElt) {
1530 elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
1531 var webSocket = getInternalData(webSocketSourceElt).webSocket;
1532 var headers = getHeaders(elt, webSocketSourceElt);
1533 var results = getInputValues(elt, 'post');
1534 var errors = results.errors;
1535 var rawParameters = results.values;
1536 var expressionVars = getExpressionVars(elt);
1537 var allParameters = mergeObjects(rawParameters, expressionVars);
1538 var filteredParameters = filterValues(allParameters, elt);
1539 filteredParameters['HEADERS'] = headers;
1540 if (errors && errors.length > 0) {
1541 triggerEvent(elt, 'htmx:validation:halted', errors);
1542 return;
1543 }
1544 webSocket.send(JSON.stringify(filteredParameters));
1545 if(shouldCancel(evt, elt)){
1546 evt.preventDefault();
1547 }
1548 });
1549 } else {
1550 triggerErrorEvent(elt, "htmx:noWebSocketSourceError");
1551 }
1552 }
1553
1554 function getWebSocketReconnectDelay(retryCount) {
1555 var delay = htmx.config.wsReconnectDelay;
1556 if (typeof delay === 'function') {
1557 // @ts-ignore
1558 return delay(retryCount);
1559 }
1560 if (delay === 'full-jitter') {
1561 var exp = Math.min(retryCount, 6);
1562 var maxDelay = 1000 * Math.pow(2, exp);
1563 return maxDelay * Math.random();
1564 }
1565 logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
1566 }
1567
1568 //====================================================================
1569 // Server Sent Events
1570 //====================================================================
1571
1572 function processSSEInfo(elt, nodeData, info) {
1573 var values = splitOnWhitespace(info);
1574 for (var i = 0; i < values.length; i++) {
1575 var value = values[i].split(/:(.+)/);
1576 if (value[0] === "connect") {
1577 processSSESource(elt, value[1]);
1578 }
1579
1580 if ((value[0] === "swap")) {
1581 processSSESwap(elt, value[1])
1582 }
1583 }
1584 }
1585
1586 function processSSESource(elt, sseSrc) {
1587 var source = htmx.createEventSource(sseSrc);
1588 source.onerror = function (e) {
1589 triggerErrorEvent(elt, "htmx:sseError", {error:e, source:source});
1590 maybeCloseSSESource(elt);
1591 };
1592 getInternalData(elt).sseEventSource = source;
1593 }
1594
1595 function processSSESwap(elt, sseEventName) {
1596 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1597 if (sseSourceElt) {
1598 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1599 var sseListener = function (event) {
1600 if (maybeCloseSSESource(sseSourceElt)) {
1601 sseEventSource.removeEventListener(sseEventName, sseListener);
1602 return;
1603 }
1604
1605 ///////////////////////////
1606 // TODO: merge this code with AJAX and WebSockets code in the future.
1607
1608 var response = event.data;
1609 withExtensions(elt, function(extension){
1610 response = extension.transformResponse(response, null, elt);
1611 });
1612
1613 var swapSpec = getSwapSpecification(elt)
1614 var target = getTarget(elt)
1615 var settleInfo = makeSettleInfo(elt);
1616
1617 selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo)
1618 settleImmediately(settleInfo.tasks)
1619 triggerEvent(elt, "htmx:sseMessage", event)
1620 };
1621
1622 getInternalData(elt).sseListener = sseListener;
1623 sseEventSource.addEventListener(sseEventName, sseListener);
1624 } else {
1625 triggerErrorEvent(elt, "htmx:noSSESourceError");
1626 }
1627 }
1628
1629 function processSSETrigger(elt, handler, sseEventName) {
1630 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1631 if (sseSourceElt) {
1632 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1633 var sseListener = function () {
1634 if (!maybeCloseSSESource(sseSourceElt)) {
1635 if (bodyContains(elt)) {
1636 handler(elt);
1637 } else {
1638 sseEventSource.removeEventListener(sseEventName, sseListener);
1639 }
1640 }
1641 };
1642 getInternalData(elt).sseListener = sseListener;
1643 sseEventSource.addEventListener(sseEventName, sseListener);
1644 } else {
1645 triggerErrorEvent(elt, "htmx:noSSESourceError");
1646 }
1647 }
1648
1649 function maybeCloseSSESource(elt) {
1650 if (!bodyContains(elt)) {
1651 getInternalData(elt).sseEventSource.close();
1652 return true;
1653 }
1654 }
1655
1656 function hasEventSource(node) {
1657 return getInternalData(node).sseEventSource != null;
1658 }
1659
1660 //====================================================================
1661
1662 function loadImmediately(elt, handler, nodeData, delay) {
1663 var load = function(){
1664 if (!nodeData.loaded) {
1665 nodeData.loaded = true;
1666 handler(elt);
1667 }
1668 }
1669 if (delay) {
1670 setTimeout(load, delay);
1671 } else {
1672 load();
1673 }
1674 }
1675
1676 function processVerbs(elt, nodeData, triggerSpecs) {
1677 var explicitAction = false;
1678 forEach(VERBS, function (verb) {
1679 if (hasAttribute(elt,'hx-' + verb)) {
1680 var path = getAttributeValue(elt, 'hx-' + verb);
1681 explicitAction = true;
1682 nodeData.path = path;
1683 nodeData.verb = verb;
1684 triggerSpecs.forEach(function(triggerSpec) {
1685 addTriggerHandler(elt, triggerSpec, nodeData, function (elt, evt) {
1686 issueAjaxRequest(verb, path, elt, evt)
1687 })
1688 });
1689 }
1690 });
1691 return explicitAction;
1692 }
1693
1694 function addTriggerHandler(elt, triggerSpec, nodeData, handler) {
1695 if (triggerSpec.sseEvent) {
1696 processSSETrigger(elt, handler, triggerSpec.sseEvent);
1697 } else if (triggerSpec.trigger === "revealed") {
1698 initScrollHandler();
1699 addEventListener(elt, handler, nodeData, triggerSpec);
1700 maybeReveal(elt);
1701 } else if (triggerSpec.trigger === "intersect") {
1702 var observerOptions = {};
1703 if (triggerSpec.root) {
1704 observerOptions.root = querySelectorExt(elt, triggerSpec.root)
1705 }
1706 if (triggerSpec.threshold) {
1707 observerOptions.threshold = parseFloat(triggerSpec.threshold);
1708 }
1709 var observer = new IntersectionObserver(function (entries) {
1710 for (var i = 0; i < entries.length; i++) {
1711 var entry = entries[i];
1712 if (entry.isIntersecting) {
1713 triggerEvent(elt, "intersect");
1714 break;
1715 }
1716 }
1717 }, observerOptions);
1718 observer.observe(elt);
1719 addEventListener(elt, handler, nodeData, triggerSpec);
1720 } else if (triggerSpec.trigger === "load") {
1721 if (!maybeFilterEvent(triggerSpec, makeEvent("load", {elt:elt}))) {
1722 loadImmediately(elt, handler, nodeData, triggerSpec.delay);
1723 }
1724 } else if (triggerSpec.pollInterval) {
1725 nodeData.polling = true;
1726 processPolling(elt, handler, triggerSpec);
1727 } else {
1728 addEventListener(elt, handler, nodeData, triggerSpec);
1729 }
1730 }
1731
1732 function evalScript(script) {
1733 if (script.type === "text/javascript" || script.type === "module" || script.type === "") {
1734 var newScript = getDocument().createElement("script");
1735 forEach(script.attributes, function (attr) {
1736 newScript.setAttribute(attr.name, attr.value);
1737 });
1738 newScript.textContent = script.textContent;
1739 newScript.async = false;
1740 if (htmx.config.inlineScriptNonce) {
1741 newScript.nonce = htmx.config.inlineScriptNonce;
1742 }
1743 var parent = script.parentElement;
1744
1745 try {
1746 parent.insertBefore(newScript, script);
1747 } catch (e) {
1748 logError(e);
1749 } finally {
1750 // remove old script element, but only if it is still in DOM
1751 if (script.parentElement) {
1752 script.parentElement.removeChild(script);
1753 }
1754 }
1755 }
1756 }
1757
1758 function processScripts(elt) {
1759 if (matches(elt, "script")) {
1760 evalScript(elt);
1761 }
1762 forEach(findAll(elt, "script"), function (script) {
1763 evalScript(script);
1764 });
1765 }
1766
1767 function hasChanceOfBeingBoosted() {
1768 return document.querySelector("[hx-boost], [data-hx-boost]");
1769 }
1770
1771 function findElementsToProcess(elt) {
1772 if (elt.querySelectorAll) {
1773 var boostedElts = hasChanceOfBeingBoosted() ? ", a, form" : "";
1774 var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," +
1775 " [data-hx-ws], [hx-ext], [data-hx-ext]");
1776 return results;
1777 } else {
1778 return [];
1779 }
1780 }
1781
1782 function initButtonTracking(form){
1783 var maybeSetLastButtonClicked = function(evt){
1784 var elt = closest(evt.target, "button, input[type='submit']");
1785 if (elt !== null) {
1786 var internalData = getInternalData(form);
1787 internalData.lastButtonClicked = elt;
1788 }
1789 };
1790
1791 // need to handle both click and focus in:
1792 // focusin - in case someone tabs in to a button and hits the space bar
1793 // click - on OSX buttons do not focus on click see https://bugs.webkit.org/show_bug.cgi?id=13724
1794
1795 form.addEventListener('click', maybeSetLastButtonClicked)
1796 form.addEventListener('focusin', maybeSetLastButtonClicked)
1797 form.addEventListener('focusout', function(evt){
1798 var internalData = getInternalData(form);
1799 internalData.lastButtonClicked = null;
1800 })
1801 }
1802
1803 function initNode(elt) {
1804 if (elt.closest && elt.closest(htmx.config.disableSelector)) {
1805 return;
1806 }
1807 var nodeData = getInternalData(elt);
1808 if (nodeData.initHash !== attributeHash(elt)) {
1809
1810 nodeData.initHash = attributeHash(elt);
1811
1812 // clean up any previously processed info
1813 deInitNode(elt);
1814
1815 triggerEvent(elt, "htmx:beforeProcessNode")
1816
1817 if (elt.value) {
1818 nodeData.lastValue = elt.value;
1819 }
1820
1821 var triggerSpecs = getTriggerSpecs(elt);
1822 var explicitAction = processVerbs(elt, nodeData, triggerSpecs);
1823
1824 if (!explicitAction && getClosestAttributeValue(elt, "hx-boost") === "true") {
1825 boostElement(elt, nodeData, triggerSpecs);
1826 }
1827
1828 if (elt.tagName === "FORM") {
1829 initButtonTracking(elt);
1830 }
1831
1832 var sseInfo = getAttributeValue(elt, 'hx-sse');
1833 if (sseInfo) {
1834 processSSEInfo(elt, nodeData, sseInfo);
1835 }
1836
1837 var wsInfo = getAttributeValue(elt, 'hx-ws');
1838 if (wsInfo) {
1839 processWebSocketInfo(elt, nodeData, wsInfo);
1840 }
1841 triggerEvent(elt, "htmx:afterProcessNode");
1842 }
1843 }
1844
1845 function processNode(elt) {
1846 elt = resolveTarget(elt);
1847 initNode(elt);
1848 forEach(findElementsToProcess(elt), function(child) { initNode(child) });
1849 }
1850
1851 //====================================================================
1852 // Event/Log Support
1853 //====================================================================
1854
1855 function kebabEventName(str) {
1856 return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
1857 }
1858
1859 function makeEvent(eventName, detail) {
1860 var evt;
1861 if (window.CustomEvent && typeof window.CustomEvent === 'function') {
1862 evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
1863 } else {
1864 evt = getDocument().createEvent('CustomEvent');
1865 evt.initCustomEvent(eventName, true, true, detail);
1866 }
1867 return evt;
1868 }
1869
1870 function triggerErrorEvent(elt, eventName, detail) {
1871 triggerEvent(elt, eventName, mergeObjects({error:eventName}, detail));
1872 }
1873
1874 function ignoreEventForLogging(eventName) {
1875 return eventName === "htmx:afterProcessNode"
1876 }
1877
1878 /**
1879 * `withExtensions` locates all active extensions for a provided element, then
1880 * executes the provided function using each of the active extensions. It should
1881 * be called internally at every extendable execution point in htmx.
1882 *
1883 * @param {HTMLElement} elt
1884 * @param {(extension:import("./htmx").HtmxExtension) => void} toDo
1885 * @returns void
1886 */
1887 function withExtensions(elt, toDo) {
1888 forEach(getExtensions(elt), function(extension){
1889 try {
1890 toDo(extension);
1891 } catch (e) {
1892 logError(e);
1893 }
1894 });
1895 }
1896
1897 function logError(msg) {
1898 if(console.error) {
1899 console.error(msg);
1900 } else if (console.log) {
1901 console.log("ERROR: ", msg);
1902 }
1903 }
1904
1905 function triggerEvent(elt, eventName, detail) {
1906 elt = resolveTarget(elt);
1907 if (detail == null) {
1908 detail = {};
1909 }
1910 detail["elt"] = elt;
1911 var event = makeEvent(eventName, detail);
1912 if (htmx.logger && !ignoreEventForLogging(eventName)) {
1913 htmx.logger(elt, eventName, detail);
1914 }
1915 if (detail.error) {
1916 logError(detail.error);
1917 triggerEvent(elt, "htmx:error", {errorInfo:detail})
1918 }
1919 var eventResult = elt.dispatchEvent(event);
1920 var kebabName = kebabEventName(eventName);
1921 if (eventResult && kebabName !== eventName) {
1922 var kebabedEvent = makeEvent(kebabName, event.detail);
1923 eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
1924 }
1925 withExtensions(elt, function (extension) {
1926 eventResult = eventResult && (extension.onEvent(eventName, event) !== false)
1927 });
1928 return eventResult;
1929 }
1930
1931 //====================================================================
1932 // History Support
1933 //====================================================================
1934 var currentPathForHistory = location.pathname+location.search;
1935
1936 function getHistoryElement() {
1937 var historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]');
1938 return historyElt || getDocument().body;
1939 }
1940
1941 function saveToHistoryCache(url, content, title, scroll) {
1942 if (!canAccessLocalStorage()) {
1943 return;
1944 }
1945
1946 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
1947 for (var i = 0; i < historyCache.length; i++) {
1948 if (historyCache[i].url === url) {
1949 historyCache.splice(i, 1);
1950 break;
1951 }
1952 }
1953 var newHistoryItem = {url:url, content: content, title:title, scroll:scroll};
1954 triggerEvent(getDocument().body, "htmx:historyItemCreated", {item:newHistoryItem, cache: historyCache})
1955 historyCache.push(newHistoryItem)
1956 while (historyCache.length > htmx.config.historyCacheSize) {
1957 historyCache.shift();
1958 }
1959 while(historyCache.length > 0){
1960 try {
1961 localStorage.setItem("htmx-history-cache", JSON.stringify(historyCache));
1962 break;
1963 } catch (e) {
1964 triggerErrorEvent(getDocument().body, "htmx:historyCacheError", {cause:e, cache: historyCache})
1965 historyCache.shift(); // shrink the cache and retry
1966 }
1967 }
1968 }
1969
1970 function getCachedHistory(url) {
1971 if (!canAccessLocalStorage()) {
1972 return null;
1973 }
1974
1975 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
1976 for (var i = 0; i < historyCache.length; i++) {
1977 if (historyCache[i].url === url) {
1978 return historyCache[i];
1979 }
1980 }
1981 return null;
1982 }
1983
1984 function cleanInnerHtmlForHistory(elt) {
1985 var className = htmx.config.requestClass;
1986 var clone = elt.cloneNode(true);
1987 forEach(findAll(clone, "." + className), function(child){
1988 removeClassFromElement(child, className);
1989 });
1990 return clone.innerHTML;
1991 }
1992
1993 function saveCurrentPageToHistory() {
1994 var elt = getHistoryElement();
1995 var path = currentPathForHistory || location.pathname+location.search;
1996
1997 // Allow history snapshot feature to be disabled where hx-history="false"
1998 // is present *anywhere* in the current document we're about to save,
1999 // so we can prevent privileged data entering the cache.
2000 // The page will still be reachable as a history entry, but htmx will fetch it
2001 // live from the server onpopstate rather than look in the localStorage cache
2002 var disableHistoryCache = getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]');
2003 if (!disableHistoryCache) {
2004 triggerEvent(getDocument().body, "htmx:beforeHistorySave", {path: path, historyElt: elt});
2005 saveToHistoryCache(path, cleanInnerHtmlForHistory(elt), getDocument().title, window.scrollY);
2006 }
2007
2008 if (htmx.config.historyEnabled) history.replaceState({htmx: true}, getDocument().title, window.location.href);
2009 }
2010
2011 function pushUrlIntoHistory(path) {
2012 // remove the cache buster parameter, if any
2013 if (htmx.config.getCacheBusterParam) {
2014 path = path.replace(/org\.htmx\.cache-buster=[^&]*&?/, '')
2015 if (path.endsWith('&') || path.endsWith("?")) {
2016 path = path.slice(0, -1);
2017 }
2018 }
2019 if(htmx.config.historyEnabled) {
2020 history.pushState({htmx:true}, "", path);
2021 }
2022 currentPathForHistory = path;
2023 }
2024
2025 function replaceUrlInHistory(path) {
2026 if(htmx.config.historyEnabled) history.replaceState({htmx:true}, "", path);
2027 currentPathForHistory = path;
2028 }
2029
2030 function settleImmediately(tasks) {
2031 forEach(tasks, function (task) {
2032 task.call();
2033 });
2034 }
2035
2036 function loadHistoryFromServer(path) {
2037 var request = new XMLHttpRequest();
2038 var details = {path: path, xhr:request};
2039 triggerEvent(getDocument().body, "htmx:historyCacheMiss", details);
2040 request.open('GET', path, true);
2041 request.setRequestHeader("HX-History-Restore-Request", "true");
2042 request.onload = function () {
2043 if (this.status >= 200 && this.status < 400) {
2044 triggerEvent(getDocument().body, "htmx:historyCacheMissLoad", details);
2045 var fragment = makeFragment(this.response);
2046 // @ts-ignore
2047 fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment;
2048 var historyElement = getHistoryElement();
2049 var settleInfo = makeSettleInfo(historyElement);
2050 var title = findTitle(this.response);
2051 if (title) {
2052 var titleElt = find("title");
2053 if (titleElt) {
2054 titleElt.innerHTML = title;
2055 } else {
2056 window.document.title = title;
2057 }
2058 }
2059 // @ts-ignore
2060 swapInnerHTML(historyElement, fragment, settleInfo)
2061 settleImmediately(settleInfo.tasks);
2062 currentPathForHistory = path;
2063 triggerEvent(getDocument().body, "htmx:historyRestore", {path: path, cacheMiss:true, serverResponse:this.response});
2064 } else {
2065 triggerErrorEvent(getDocument().body, "htmx:historyCacheMissLoadError", details);
2066 }
2067 };
2068 request.send();
2069 }
2070
2071 function restoreHistory(path) {
2072 saveCurrentPageToHistory();
2073 path = path || location.pathname+location.search;
2074 var cached = getCachedHistory(path);
2075 if (cached) {
2076 var fragment = makeFragment(cached.content);
2077 var historyElement = getHistoryElement();
2078 var settleInfo = makeSettleInfo(historyElement);
2079 swapInnerHTML(historyElement, fragment, settleInfo)
2080 settleImmediately(settleInfo.tasks);
2081 document.title = cached.title;
2082 window.scrollTo(0, cached.scroll);
2083 currentPathForHistory = path;
2084 triggerEvent(getDocument().body, "htmx:historyRestore", {path:path, item:cached});
2085 } else {
2086 if (htmx.config.refreshOnHistoryMiss) {
2087
2088 // @ts-ignore: optional parameter in reload() function throws error
2089 window.location.reload(true);
2090 } else {
2091 loadHistoryFromServer(path);
2092 }
2093 }
2094 }
2095
2096 function addRequestIndicatorClasses(elt) {
2097 var indicators = findAttributeTargets(elt, 'hx-indicator');
2098 if (indicators == null) {
2099 indicators = [elt];
2100 }
2101 forEach(indicators, function (ic) {
2102 var internalData = getInternalData(ic);
2103 internalData.requestCount = (internalData.requestCount || 0) + 1;
2104 ic.classList["add"].call(ic.classList, htmx.config.requestClass);
2105 });
2106 return indicators;
2107 }
2108
2109 function removeRequestIndicatorClasses(indicators) {
2110 forEach(indicators, function (ic) {
2111 var internalData = getInternalData(ic);
2112 internalData.requestCount = (internalData.requestCount || 0) - 1;
2113 if (internalData.requestCount === 0) {
2114 ic.classList["remove"].call(ic.classList, htmx.config.requestClass);
2115 }
2116 });
2117 }
2118
2119 //====================================================================
2120 // Input Value Processing
2121 //====================================================================
2122
2123 function haveSeenNode(processed, elt) {
2124 for (var i = 0; i < processed.length; i++) {
2125 var node = processed[i];
2126 if (node.isSameNode(elt)) {
2127 return true;
2128 }
2129 }
2130 return false;
2131 }
2132
2133 function shouldInclude(elt) {
2134 if(elt.name === "" || elt.name == null || elt.disabled) {
2135 return false;
2136 }
2137 // ignore "submitter" types (see jQuery src/serialize.js)
2138 if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
2139 return false;
2140 }
2141 if (elt.type === "checkbox" || elt.type === "radio" ) {
2142 return elt.checked;
2143 }
2144 return true;
2145 }
2146
2147 function processInputValue(processed, values, errors, elt, validate) {
2148 if (elt == null || haveSeenNode(processed, elt)) {
2149 return;
2150 } else {
2151 processed.push(elt);
2152 }
2153 if (shouldInclude(elt)) {
2154 var name = getRawAttribute(elt,"name");
2155 var value = elt.value;
2156 if (elt.multiple) {
2157 value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value });
2158 }
2159 // include file inputs
2160 if (elt.files) {
2161 value = toArray(elt.files);
2162 }
2163 // This is a little ugly because both the current value of the named value in the form
2164 // and the new value could be arrays, so we have to handle all four cases :/
2165 if (name != null && value != null) {
2166 var current = values[name];
2167 if (current !== undefined) {
2168 if (Array.isArray(current)) {
2169 if (Array.isArray(value)) {
2170 values[name] = current.concat(value);
2171 } else {
2172 current.push(value);
2173 }
2174 } else {
2175 if (Array.isArray(value)) {
2176 values[name] = [current].concat(value);
2177 } else {
2178 values[name] = [current, value];
2179 }
2180 }
2181 } else {
2182 values[name] = value;
2183 }
2184 }
2185 if (validate) {
2186 validateElement(elt, errors);
2187 }
2188 }
2189 if (matches(elt, 'form')) {
2190 var inputs = elt.elements;
2191 forEach(inputs, function(input) {
2192 processInputValue(processed, values, errors, input, validate);
2193 });
2194 }
2195 }
2196
2197 function validateElement(element, errors) {
2198 if (element.willValidate) {
2199 triggerEvent(element, "htmx:validation:validate")
2200 if (!element.checkValidity()) {
2201 errors.push({elt: element, message:element.validationMessage, validity:element.validity});
2202 triggerEvent(element, "htmx:validation:failed", {message:element.validationMessage, validity:element.validity})
2203 }
2204 }
2205 }
2206
2207 /**
2208 * @param {HTMLElement} elt
2209 * @param {string} verb
2210 */
2211 function getInputValues(elt, verb) {
2212 var processed = [];
2213 var values = {};
2214 var formValues = {};
2215 var errors = [];
2216 var internalData = getInternalData(elt);
2217
2218 // only validate when form is directly submitted and novalidate or formnovalidate are not set
2219 // or if the element has an explicit hx-validate="true" on it
2220 var validate = (matches(elt, 'form') && elt.noValidate !== true) || getAttributeValue(elt, "hx-validate") === "true";
2221 if (internalData.lastButtonClicked) {
2222 validate = validate && internalData.lastButtonClicked.formNoValidate !== true;
2223 }
2224
2225 // for a non-GET include the closest form
2226 if (verb !== 'get') {
2227 processInputValue(processed, formValues, errors, closest(elt, 'form'), validate);
2228 }
2229
2230 // include the element itself
2231 processInputValue(processed, values, errors, elt, validate);
2232
2233 // if a button or submit was clicked last, include its value
2234 if (internalData.lastButtonClicked) {
2235 var name = getRawAttribute(internalData.lastButtonClicked,"name");
2236 if (name) {
2237 values[name] = internalData.lastButtonClicked.value;
2238 }
2239 }
2240
2241 // include any explicit includes
2242 var includes = findAttributeTargets(elt, "hx-include");
2243 forEach(includes, function(node) {
2244 processInputValue(processed, values, errors, node, validate);
2245 // if a non-form is included, include any input values within it
2246 if (!matches(node, 'form')) {
2247 forEach(node.querySelectorAll(INPUT_SELECTOR), function (descendant) {
2248 processInputValue(processed, values, errors, descendant, validate);
2249 })
2250 }
2251 });
2252
2253 // form values take precedence, overriding the regular values
2254 values = mergeObjects(values, formValues);
2255
2256 return {errors:errors, values:values};
2257 }
2258
2259 function appendParam(returnStr, name, realValue) {
2260 if (returnStr !== "") {
2261 returnStr += "&";
2262 }
2263 if (String(realValue) === "[object Object]") {
2264 realValue = JSON.stringify(realValue);
2265 }
2266 var s = encodeURIComponent(realValue);
2267 returnStr += encodeURIComponent(name) + "=" + s;
2268 return returnStr;
2269 }
2270
2271 function urlEncode(values) {
2272 var returnStr = "";
2273 for (var name in values) {
2274 if (values.hasOwnProperty(name)) {
2275 var value = values[name];
2276 if (Array.isArray(value)) {
2277 forEach(value, function(v) {
2278 returnStr = appendParam(returnStr, name, v);
2279 });
2280 } else {
2281 returnStr = appendParam(returnStr, name, value);
2282 }
2283 }
2284 }
2285 return returnStr;
2286 }
2287
2288 function makeFormData(values) {
2289 var formData = new FormData();
2290 for (var name in values) {
2291 if (values.hasOwnProperty(name)) {
2292 var value = values[name];
2293 if (Array.isArray(value)) {
2294 forEach(value, function(v) {
2295 formData.append(name, v);
2296 });
2297 } else {
2298 formData.append(name, value);
2299 }
2300 }
2301 }
2302 return formData;
2303 }
2304
2305 //====================================================================
2306 // Ajax
2307 //====================================================================
2308
2309 /**
2310 * @param {HTMLElement} elt
2311 * @param {HTMLElement} target
2312 * @param {string} prompt
2313 * @returns {Object} // TODO: Define/Improve HtmxHeaderSpecification
2314 */
2315 function getHeaders(elt, target, prompt) {
2316 var headers = {
2317 "HX-Request" : "true",
2318 "HX-Trigger" : getRawAttribute(elt, "id"),
2319 "HX-Trigger-Name" : getRawAttribute(elt, "name"),
2320 "HX-Target" : getAttributeValue(target, "id"),
2321 "HX-Current-URL" : getDocument().location.href,
2322 }
2323 getValuesForElement(elt, "hx-headers", false, headers)
2324 if (prompt !== undefined) {
2325 headers["HX-Prompt"] = prompt;
2326 }
2327 if (getInternalData(elt).boosted) {
2328 headers["HX-Boosted"] = "true";
2329 }
2330 return headers;
2331 }
2332
2333 /**
2334 * filterValues takes an object containing form input values
2335 * and returns a new object that only contains keys that are
2336 * specified by the closest "hx-params" attribute
2337 * @param {Object} inputValues
2338 * @param {HTMLElement} elt
2339 * @returns {Object}
2340 */
2341 function filterValues(inputValues, elt) {
2342 var paramsValue = getClosestAttributeValue(elt, "hx-params");
2343 if (paramsValue) {
2344 if (paramsValue === "none") {
2345 return {};
2346 } else if (paramsValue === "*") {
2347 return inputValues;
2348 } else if(paramsValue.indexOf("not ") === 0) {
2349 forEach(paramsValue.substr(4).split(","), function (name) {
2350 name = name.trim();
2351 delete inputValues[name];
2352 });
2353 return inputValues;
2354 } else {
2355 var newValues = {}
2356 forEach(paramsValue.split(","), function (name) {
2357 name = name.trim();
2358 newValues[name] = inputValues[name];
2359 });
2360 return newValues;
2361 }
2362 } else {
2363 return inputValues;
2364 }
2365 }
2366
2367 function isAnchorLink(elt) {
2368 return getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf("#") >=0
2369 }
2370
2371 /**
2372 *
2373 * @param {HTMLElement} elt
2374 * @param {string} swapInfoOverride
2375 * @returns {import("./htmx").HtmxSwapSpecification}
2376 */
2377 function getSwapSpecification(elt, swapInfoOverride) {
2378 var swapInfo = swapInfoOverride ? swapInfoOverride : getClosestAttributeValue(elt, "hx-swap");
2379 var swapSpec = {
2380 "swapStyle" : getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle,
2381 "swapDelay" : htmx.config.defaultSwapDelay,
2382 "settleDelay" : htmx.config.defaultSettleDelay
2383 }
2384 if (getInternalData(elt).boosted && !isAnchorLink(elt)) {
2385 swapSpec["show"] = "top"
2386 }
2387 if (swapInfo) {
2388 var split = splitOnWhitespace(swapInfo);
2389 if (split.length > 0) {
2390 swapSpec["swapStyle"] = split[0];
2391 for (var i = 1; i < split.length; i++) {
2392 var modifier = split[i];
2393 if (modifier.indexOf("swap:") === 0) {
2394 swapSpec["swapDelay"] = parseInterval(modifier.substr(5));
2395 }
2396 if (modifier.indexOf("settle:") === 0) {
2397 swapSpec["settleDelay"] = parseInterval(modifier.substr(7));
2398 }
2399 if (modifier.indexOf("scroll:") === 0) {
2400 var scrollSpec = modifier.substr(7);
2401 var splitSpec = scrollSpec.split(":");
2402 var scrollVal = splitSpec.pop();
2403 var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
2404 swapSpec["scroll"] = scrollVal;
2405 swapSpec["scrollTarget"] = selectorVal;
2406 }
2407 if (modifier.indexOf("show:") === 0) {
2408 var showSpec = modifier.substr(5);
2409 var splitSpec = showSpec.split(":");
2410 var showVal = splitSpec.pop();
2411 var selectorVal = splitSpec.length > 0 ? splitSpec.join(":") : null;
2412 swapSpec["show"] = showVal;
2413 swapSpec["showTarget"] = selectorVal;
2414 }
2415 if (modifier.indexOf("focus-scroll:") === 0) {
2416 var focusScrollVal = modifier.substr("focus-scroll:".length);
2417 swapSpec["focusScroll"] = focusScrollVal == "true";
2418 }
2419 }
2420 }
2421 }
2422 return swapSpec;
2423 }
2424
2425 function usesFormData(elt) {
2426 return getClosestAttributeValue(elt, "hx-encoding") === "multipart/form-data" ||
2427 (matches(elt, "form") && getRawAttribute(elt, 'enctype') === "multipart/form-data");
2428 }
2429
2430 function encodeParamsForBody(xhr, elt, filteredParameters) {
2431 var encodedParameters = null;
2432 withExtensions(elt, function (extension) {
2433 if (encodedParameters == null) {
2434 encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt);
2435 }
2436 });
2437 if (encodedParameters != null) {
2438 return encodedParameters;
2439 } else {
2440 if (usesFormData(elt)) {
2441 return makeFormData(filteredParameters);
2442 } else {
2443 return urlEncode(filteredParameters);
2444 }
2445 }
2446 }
2447
2448 /**
2449 *
2450 * @param {Element} target
2451 * @returns {import("./htmx").HtmxSettleInfo}
2452 */
2453 function makeSettleInfo(target) {
2454 return {tasks: [], elts: [target]};
2455 }
2456
2457 function updateScrollState(content, swapSpec) {
2458 var first = content[0];
2459 var last = content[content.length - 1];
2460 if (swapSpec.scroll) {
2461 var target = null;
2462 if (swapSpec.scrollTarget) {
2463 target = querySelectorExt(first, swapSpec.scrollTarget);
2464 }
2465 if (swapSpec.scroll === "top" && (first || target)) {
2466 target = target || first;
2467 target.scrollTop = 0;
2468 }
2469 if (swapSpec.scroll === "bottom" && (last || target)) {
2470 target = target || last;
2471 target.scrollTop = target.scrollHeight;
2472 }
2473 }
2474 if (swapSpec.show) {
2475 var target = null;
2476 if (swapSpec.showTarget) {
2477 var targetStr = swapSpec.showTarget;
2478 if (swapSpec.showTarget === "window") {
2479 targetStr = "body";
2480 }
2481 target = querySelectorExt(first, targetStr);
2482 }
2483 if (swapSpec.show === "top" && (first || target)) {
2484 target = target || first;
2485 target.scrollIntoView({block:'start', behavior: htmx.config.scrollBehavior});
2486 }
2487 if (swapSpec.show === "bottom" && (last || target)) {
2488 target = target || last;
2489 target.scrollIntoView({block:'end', behavior: htmx.config.scrollBehavior});
2490 }
2491 }
2492 }
2493
2494 /**
2495 * @param {HTMLElement} elt
2496 * @param {string} attr
2497 * @param {boolean=} evalAsDefault
2498 * @param {Object=} values
2499 * @returns {Object}
2500 */
2501 function getValuesForElement(elt, attr, evalAsDefault, values) {
2502 if (values == null) {
2503 values = {};
2504 }
2505 if (elt == null) {
2506 return values;
2507 }
2508 var attributeValue = getAttributeValue(elt, attr);
2509 if (attributeValue) {
2510 var str = attributeValue.trim();
2511 var evaluateValue = evalAsDefault;
2512 if (str === "unset") {
2513 return null;
2514 }
2515 if (str.indexOf("javascript:") === 0) {
2516 str = str.substr(11);
2517 evaluateValue = true;
2518 } else if (str.indexOf("js:") === 0) {
2519 str = str.substr(3);
2520 evaluateValue = true;
2521 }
2522 if (str.indexOf('{') !== 0) {
2523 str = "{" + str + "}";
2524 }
2525 var varsValues;
2526 if (evaluateValue) {
2527 varsValues = maybeEval(elt,function () {return Function("return (" + str + ")")();}, {});
2528 } else {
2529 varsValues = parseJSON(str);
2530 }
2531 for (var key in varsValues) {
2532 if (varsValues.hasOwnProperty(key)) {
2533 if (values[key] == null) {
2534 values[key] = varsValues[key];
2535 }
2536 }
2537 }
2538 }
2539 return getValuesForElement(parentElt(elt), attr, evalAsDefault, values);
2540 }
2541
2542 function maybeEval(elt, toEval, defaultVal) {
2543 if (htmx.config.allowEval) {
2544 return toEval();
2545 } else {
2546 triggerErrorEvent(elt, 'htmx:evalDisallowedError');
2547 return defaultVal;
2548 }
2549 }
2550
2551 /**
2552 * @param {HTMLElement} elt
2553 * @param {*} expressionVars
2554 * @returns
2555 */
2556 function getHXVarsForElement(elt, expressionVars) {
2557 return getValuesForElement(elt, "hx-vars", true, expressionVars);
2558 }
2559
2560 /**
2561 * @param {HTMLElement} elt
2562 * @param {*} expressionVars
2563 * @returns
2564 */
2565 function getHXValsForElement(elt, expressionVars) {
2566 return getValuesForElement(elt, "hx-vals", false, expressionVars);
2567 }
2568
2569 /**
2570 * @param {HTMLElement} elt
2571 * @returns {Object}
2572 */
2573 function getExpressionVars(elt) {
2574 return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt));
2575 }
2576
2577 function safelySetHeaderValue(xhr, header, headerValue) {
2578 if (headerValue !== null) {
2579 try {
2580 xhr.setRequestHeader(header, headerValue);
2581 } catch (e) {
2582 // On an exception, try to set the header URI encoded instead
2583 xhr.setRequestHeader(header, encodeURIComponent(headerValue));
2584 xhr.setRequestHeader(header + "-URI-AutoEncoded", "true");
2585 }
2586 }
2587 }
2588
2589 function getPathFromResponse(xhr) {
2590 // NB: IE11 does not support this stuff
2591 if (xhr.responseURL && typeof(URL) !== "undefined") {
2592 try {
2593 var url = new URL(xhr.responseURL);
2594 return url.pathname + url.search;
2595 } catch (e) {
2596 triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL});
2597 }
2598 }
2599 }
2600
2601 function hasHeader(xhr, regexp) {
2602 return xhr.getAllResponseHeaders().match(regexp);
2603 }
2604
2605 function ajaxHelper(verb, path, context) {
2606 verb = verb.toLowerCase();
2607 if (context) {
2608 if (context instanceof Element || isType(context, 'String')) {
2609 return issueAjaxRequest(verb, path, null, null, {
2610 targetOverride: resolveTarget(context),
2611 returnPromise: true
2612 });
2613 } else {
2614 return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
2615 {
2616 handler : context.handler,
2617 headers : context.headers,
2618 values : context.values,
2619 targetOverride: resolveTarget(context.target),
2620 swapOverride: context.swap,
2621 returnPromise: true
2622 });
2623 }
2624 } else {
2625 return issueAjaxRequest(verb, path, null, null, {
2626 returnPromise: true
2627 });
2628 }
2629 }
2630
2631 function hierarchyForElt(elt) {
2632 var arr = [];
2633 while (elt) {
2634 arr.push(elt);
2635 elt = elt.parentElement;
2636 }
2637 return arr;
2638 }
2639
2640 function issueAjaxRequest(verb, path, elt, event, etc, confirmed) {
2641 var resolve = null;
2642 var reject = null;
2643 etc = etc != null ? etc : {};
2644 if(etc.returnPromise && typeof Promise !== "undefined"){
2645 var promise = new Promise(function (_resolve, _reject) {
2646 resolve = _resolve;
2647 reject = _reject;
2648 });
2649 }
2650 if(elt == null) {
2651 elt = getDocument().body;
2652 }
2653 var responseHandler = etc.handler || handleAjaxResponse;
2654
2655 if (!bodyContains(elt)) {
2656 return; // do not issue requests for elements removed from the DOM
2657 }
2658 var target = etc.targetOverride || getTarget(elt);
2659 if (target == null || target == DUMMY_ELT) {
2660 triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
2661 return;
2662 }
2663
2664 // allow event-based confirmation w/ a callback
2665 if (!confirmed) {
2666 var issueRequest = function() {
2667 return issueAjaxRequest(verb, path, elt, event, etc, true);
2668 }
2669 var confirmDetails = {target: target, elt: elt, path: path, verb: verb, triggeringEvent: event, etc: etc, issueRequest: issueRequest};
2670 if (triggerEvent(elt, 'htmx:confirm', confirmDetails) === false) {
2671 return;
2672 }
2673 }
2674
2675 var syncElt = elt;
2676 var eltData = getInternalData(elt);
2677 var syncStrategy = getClosestAttributeValue(elt, "hx-sync");
2678 var queueStrategy = null;
2679 var abortable = false;
2680 if (syncStrategy) {
2681 var syncStrings = syncStrategy.split(":");
2682 var selector = syncStrings[0].trim();
2683 if (selector === "this") {
2684 syncElt = findThisElement(elt, 'hx-sync');
2685 } else {
2686 syncElt = querySelectorExt(elt, selector);
2687 }
2688 // default to the drop strategy
2689 syncStrategy = (syncStrings[1] || 'drop').trim();
2690 eltData = getInternalData(syncElt);
2691 if (syncStrategy === "drop" && eltData.xhr && eltData.abortable !== true) {
2692 return;
2693 } else if (syncStrategy === "abort") {
2694 if (eltData.xhr) {
2695 return;
2696 } else {
2697 abortable = true;
2698 }
2699 } else if (syncStrategy === "replace") {
2700 triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
2701 } else if (syncStrategy.indexOf("queue") === 0) {
2702 var queueStrArray = syncStrategy.split(" ");
2703 queueStrategy = (queueStrArray[1] || "last").trim();
2704 }
2705 }
2706
2707 if (eltData.xhr) {
2708 if (eltData.abortable) {
2709 triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
2710 } else {
2711 if(queueStrategy == null){
2712 if (event) {
2713 var eventData = getInternalData(event);
2714 if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
2715 queueStrategy = eventData.triggerSpec.queue;
2716 }
2717 }
2718 if (queueStrategy == null) {
2719 queueStrategy = "last";
2720 }
2721 }
2722 if (eltData.queuedRequests == null) {
2723 eltData.queuedRequests = [];
2724 }
2725 if (queueStrategy === "first" && eltData.queuedRequests.length === 0) {
2726 eltData.queuedRequests.push(function () {
2727 issueAjaxRequest(verb, path, elt, event, etc)
2728 });
2729 } else if (queueStrategy === "all") {
2730 eltData.queuedRequests.push(function () {
2731 issueAjaxRequest(verb, path, elt, event, etc)
2732 });
2733 } else if (queueStrategy === "last") {
2734 eltData.queuedRequests = []; // dump existing queue
2735 eltData.queuedRequests.push(function () {
2736 issueAjaxRequest(verb, path, elt, event, etc)
2737 });
2738 }
2739 return;
2740 }
2741 }
2742
2743 var xhr = new XMLHttpRequest();
2744 eltData.xhr = xhr;
2745 eltData.abortable = abortable;
2746 var endRequestLock = function(){
2747 eltData.xhr = null;
2748 eltData.abortable = false;
2749 if (eltData.queuedRequests != null &&
2750 eltData.queuedRequests.length > 0) {
2751 var queuedRequest = eltData.queuedRequests.shift();
2752 queuedRequest();
2753 }
2754 }
2755 var promptQuestion = getClosestAttributeValue(elt, "hx-prompt");
2756 if (promptQuestion) {
2757 var promptResponse = prompt(promptQuestion);
2758 // prompt returns null if cancelled and empty string if accepted with no entry
2759 if (promptResponse === null ||
2760 !triggerEvent(elt, 'htmx:prompt', {prompt: promptResponse, target:target})) {
2761 maybeCall(resolve);
2762 endRequestLock();
2763 return promise;
2764 }
2765 }
2766
2767 var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm");
2768 if (confirmQuestion) {
2769 if(!confirm(confirmQuestion)) {
2770 maybeCall(resolve);
2771 endRequestLock()
2772 return promise;
2773 }
2774 }
2775
2776
2777 var headers = getHeaders(elt, target, promptResponse);
2778 if (etc.headers) {
2779 headers = mergeObjects(headers, etc.headers);
2780 }
2781 var results = getInputValues(elt, verb);
2782 var errors = results.errors;
2783 var rawParameters = results.values;
2784 if (etc.values) {
2785 rawParameters = mergeObjects(rawParameters, etc.values);
2786 }
2787 var expressionVars = getExpressionVars(elt);
2788 var allParameters = mergeObjects(rawParameters, expressionVars);
2789 var filteredParameters = filterValues(allParameters, elt);
2790
2791 if (verb !== 'get' && !usesFormData(elt)) {
2792 headers['Content-Type'] = 'application/x-www-form-urlencoded';
2793 }
2794
2795 if (htmx.config.getCacheBusterParam && verb === 'get') {
2796 filteredParameters['org.htmx.cache-buster'] = getRawAttribute(target, "id") || "true";
2797 }
2798
2799 // behavior of anchors w/ empty href is to use the current URL
2800 if (path == null || path === "") {
2801 path = getDocument().location.href;
2802 }
2803
2804
2805 var requestAttrValues = getValuesForElement(elt, 'hx-request');
2806
2807 var eltIsBoosted = getInternalData(elt).boosted;
2808 var requestConfig = {
2809 boosted: eltIsBoosted,
2810 parameters: filteredParameters,
2811 unfilteredParameters: allParameters,
2812 headers:headers,
2813 target:target,
2814 verb:verb,
2815 errors:errors,
2816 withCredentials: etc.credentials || requestAttrValues.credentials || htmx.config.withCredentials,
2817 timeout: etc.timeout || requestAttrValues.timeout || htmx.config.timeout,
2818 path:path,
2819 triggeringEvent:event
2820 };
2821
2822 if(!triggerEvent(elt, 'htmx:configRequest', requestConfig)){
2823 maybeCall(resolve);
2824 endRequestLock();
2825 return promise;
2826 }
2827
2828 // copy out in case the object was overwritten
2829 path = requestConfig.path;
2830 verb = requestConfig.verb;
2831 headers = requestConfig.headers;
2832 filteredParameters = requestConfig.parameters;
2833 errors = requestConfig.errors;
2834
2835 if(errors && errors.length > 0){
2836 triggerEvent(elt, 'htmx:validation:halted', requestConfig)
2837 maybeCall(resolve);
2838 endRequestLock();
2839 return promise;
2840 }
2841
2842 var splitPath = path.split("#");
2843 var pathNoAnchor = splitPath[0];
2844 var anchor = splitPath[1];
2845 var finalPathForGet = null;
2846 if (verb === 'get') {
2847 finalPathForGet = pathNoAnchor;
2848 var values = Object.keys(filteredParameters).length !== 0;
2849 if (values) {
2850 if (finalPathForGet.indexOf("?") < 0) {
2851 finalPathForGet += "?";
2852 } else {
2853 finalPathForGet += "&";
2854 }
2855 finalPathForGet += urlEncode(filteredParameters);
2856 if (anchor) {
2857 finalPathForGet += "#" + anchor;
2858 }
2859 }
2860 xhr.open('GET', finalPathForGet, true);
2861 } else {
2862 xhr.open(verb.toUpperCase(), path, true);
2863 }
2864
2865 xhr.overrideMimeType("text/html");
2866 xhr.withCredentials = requestConfig.withCredentials;
2867 xhr.timeout = requestConfig.timeout;
2868
2869 // request headers
2870 if (requestAttrValues.noHeaders) {
2871 // ignore all headers
2872 } else {
2873 for (var header in headers) {
2874 if (headers.hasOwnProperty(header)) {
2875 var headerValue = headers[header];
2876 safelySetHeaderValue(xhr, header, headerValue);
2877 }
2878 }
2879 }
2880
2881 var responseInfo = {
2882 xhr: xhr, target: target, requestConfig: requestConfig, etc: etc, boosted: eltIsBoosted,
2883 pathInfo: {
2884 requestPath: path,
2885 finalRequestPath: finalPathForGet || path,
2886 anchor: anchor
2887 }
2888 };
2889
2890 xhr.onload = function () {
2891 try {
2892 var hierarchy = hierarchyForElt(elt);
2893 responseInfo.pathInfo.responsePath = getPathFromResponse(xhr);
2894 responseHandler(elt, responseInfo);
2895 removeRequestIndicatorClasses(indicators);
2896 triggerEvent(elt, 'htmx:afterRequest', responseInfo);
2897 triggerEvent(elt, 'htmx:afterOnLoad', responseInfo);
2898 // if the body no longer contains the element, trigger the event on the closest parent
2899 // remaining in the DOM
2900 if (!bodyContains(elt)) {
2901 var secondaryTriggerElt = null;
2902 while (hierarchy.length > 0 && secondaryTriggerElt == null) {
2903 var parentEltInHierarchy = hierarchy.shift();
2904 if (bodyContains(parentEltInHierarchy)) {
2905 secondaryTriggerElt = parentEltInHierarchy;
2906 }
2907 }
2908 if (secondaryTriggerElt) {
2909 triggerEvent(secondaryTriggerElt, 'htmx:afterRequest', responseInfo);
2910 triggerEvent(secondaryTriggerElt, 'htmx:afterOnLoad', responseInfo);
2911 }
2912 }
2913 maybeCall(resolve);
2914 endRequestLock();
2915 } catch (e) {
2916 triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo));
2917 throw e;
2918 }
2919 }
2920 xhr.onerror = function () {
2921 removeRequestIndicatorClasses(indicators);
2922 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2923 triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
2924 maybeCall(reject);
2925 endRequestLock();
2926 }
2927 xhr.onabort = function() {
2928 removeRequestIndicatorClasses(indicators);
2929 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2930 triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo);
2931 maybeCall(reject);
2932 endRequestLock();
2933 }
2934 xhr.ontimeout = function() {
2935 removeRequestIndicatorClasses(indicators);
2936 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2937 triggerErrorEvent(elt, 'htmx:timeout', responseInfo);
2938 maybeCall(reject);
2939 endRequestLock();
2940 }
2941 if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)){
2942 maybeCall(resolve);
2943 endRequestLock()
2944 return promise
2945 }
2946 var indicators = addRequestIndicatorClasses(elt);
2947
2948 forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
2949 forEach([xhr, xhr.upload], function (target) {
2950 target.addEventListener(eventName, function(event){
2951 triggerEvent(elt, "htmx:xhr:" + eventName, {
2952 lengthComputable:event.lengthComputable,
2953 loaded:event.loaded,
2954 total:event.total
2955 });
2956 })
2957 });
2958 });
2959 triggerEvent(elt, 'htmx:beforeSend', responseInfo);
2960 xhr.send(verb === 'get' ? null : encodeParamsForBody(xhr, elt, filteredParameters));
2961 return promise;
2962 }
2963
2964 function determineHistoryUpdates(elt, responseInfo) {
2965
2966 var xhr = responseInfo.xhr;
2967
2968 //===========================================
2969 // First consult response headers
2970 //===========================================
2971 var pathFromHeaders = null;
2972 var typeFromHeaders = null;
2973 if (hasHeader(xhr,/HX-Push:/i)) {
2974 pathFromHeaders = xhr.getResponseHeader("HX-Push");
2975 typeFromHeaders = "push";
2976 } else if (hasHeader(xhr,/HX-Push-Url:/i)) {
2977 pathFromHeaders = xhr.getResponseHeader("HX-Push-Url");
2978 typeFromHeaders = "push";
2979 } else if (hasHeader(xhr,/HX-Replace-Url:/i)) {
2980 pathFromHeaders = xhr.getResponseHeader("HX-Replace-Url");
2981 typeFromHeaders = "replace";
2982 }
2983
2984 // if there was a response header, that has priority
2985 if (pathFromHeaders) {
2986 if (pathFromHeaders === "false") {
2987 return {}
2988 } else {
2989 return {
2990 type: typeFromHeaders,
2991 path : pathFromHeaders
2992 }
2993 }
2994 }
2995
2996 //===========================================
2997 // Next resolve via DOM values
2998 //===========================================
2999 var requestPath = responseInfo.pathInfo.finalRequestPath;
3000 var responsePath = responseInfo.pathInfo.responsePath;
3001
3002 var pushUrl = getClosestAttributeValue(elt, "hx-push-url");
3003 var replaceUrl = getClosestAttributeValue(elt, "hx-replace-url");
3004 var elementIsBoosted = getInternalData(elt).boosted;
3005
3006 var saveType = null;
3007 var path = null;
3008
3009 if (pushUrl) {
3010 saveType = "push";
3011 path = pushUrl;
3012 } else if (replaceUrl) {
3013 saveType = "replace";
3014 path = replaceUrl;
3015 } else if (elementIsBoosted) {
3016 saveType = "push";
3017 path = responsePath || requestPath; // if there is no response path, go with the original request path
3018 }
3019
3020 if (path) {
3021 // false indicates no push, return empty object
3022 if (path === "false") {
3023 return {};
3024 }
3025
3026 // true indicates we want to follow wherever the server ended up sending us
3027 if (path === "true") {
3028 path = responsePath || requestPath; // if there is no response path, go with the original request path
3029 }
3030
3031 // restore any anchor associated with the request
3032 if (responseInfo.pathInfo.anchor &&
3033 path.indexOf("#") === -1) {
3034 path = path + "#" + responseInfo.pathInfo.anchor;
3035 }
3036
3037 return {
3038 type:saveType,
3039 path: path
3040 }
3041 } else {
3042 return {};
3043 }
3044 }
3045
3046 function handleAjaxResponse(elt, responseInfo) {
3047 var xhr = responseInfo.xhr;
3048 var target = responseInfo.target;
3049 var etc = responseInfo.etc;
3050
3051 if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return;
3052
3053 if (hasHeader(xhr, /HX-Trigger:/i)) {
3054 handleTrigger(xhr, "HX-Trigger", elt);
3055 }
3056
3057 if (hasHeader(xhr, /HX-Location:/i)) {
3058 saveCurrentPageToHistory();
3059 var redirectPath = xhr.getResponseHeader("HX-Location");
3060 var swapSpec;
3061 if (redirectPath.indexOf("{") === 0) {
3062 swapSpec = parseJSON(redirectPath);
3063 // what's the best way to throw an error if the user didn't include this
3064 redirectPath = swapSpec['path'];
3065 delete swapSpec['path'];
3066 }
3067 ajaxHelper('GET', redirectPath, swapSpec).then(function(){
3068 pushUrlIntoHistory(redirectPath);
3069 });
3070 return;
3071 }
3072
3073 if (hasHeader(xhr, /HX-Redirect:/i)) {
3074 location.href = xhr.getResponseHeader("HX-Redirect");
3075 return;
3076 }
3077
3078 if (hasHeader(xhr,/HX-Refresh:/i)) {
3079 if ("true" === xhr.getResponseHeader("HX-Refresh")) {
3080 location.reload();
3081 return;
3082 }
3083 }
3084
3085 if (hasHeader(xhr,/HX-Retarget:/i)) {
3086 responseInfo.target = getDocument().querySelector(xhr.getResponseHeader("HX-Retarget"));
3087 }
3088
3089 var historyUpdate = determineHistoryUpdates(elt, responseInfo);
3090
3091 // by default htmx only swaps on 200 return codes and does not swap
3092 // on 204 'No Content'
3093 // this can be ovverriden by responding to the htmx:beforeSwap event and
3094 // overriding the detail.shouldSwap property
3095 var shouldSwap = xhr.status >= 200 && xhr.status < 400 && xhr.status !== 204;
3096 var serverResponse = xhr.response;
3097 var isError = xhr.status >= 400;
3098 var beforeSwapDetails = mergeObjects({shouldSwap: shouldSwap, serverResponse:serverResponse, isError:isError}, responseInfo);
3099 if (!triggerEvent(target, 'htmx:beforeSwap', beforeSwapDetails)) return;
3100
3101 target = beforeSwapDetails.target; // allow re-targeting
3102 serverResponse = beforeSwapDetails.serverResponse; // allow updating content
3103 isError = beforeSwapDetails.isError; // allow updating error
3104
3105 responseInfo.target = target; // Make updated target available to response events
3106 responseInfo.failed = isError; // Make failed property available to response events
3107 responseInfo.successful = !isError; // Make successful property available to response events
3108
3109 if (beforeSwapDetails.shouldSwap) {
3110 if (xhr.status === 286) {
3111 cancelPolling(elt);
3112 }
3113
3114 withExtensions(elt, function (extension) {
3115 serverResponse = extension.transformResponse(serverResponse, xhr, elt);
3116 });
3117
3118 // Save current page if there will be a history update
3119 if (historyUpdate.type) {
3120 saveCurrentPageToHistory();
3121 }
3122
3123 var swapOverride = etc.swapOverride;
3124 if (hasHeader(xhr,/HX-Reswap:/i)) {
3125 swapOverride = xhr.getResponseHeader("HX-Reswap");
3126 }
3127 var swapSpec = getSwapSpecification(elt, swapOverride);
3128
3129 target.classList.add(htmx.config.swappingClass);
3130 var doSwap = function () {
3131 try {
3132
3133 var activeElt = document.activeElement;
3134 var selectionInfo = {};
3135 try {
3136 selectionInfo = {
3137 elt: activeElt,
3138 // @ts-ignore
3139 start: activeElt ? activeElt.selectionStart : null,
3140 // @ts-ignore
3141 end: activeElt ? activeElt.selectionEnd : null
3142 };
3143 } catch (e) {
3144 // safari issue - see https://github.com/microsoft/playwright/issues/5894
3145 }
3146
3147 var settleInfo = makeSettleInfo(target);
3148 selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo);
3149
3150 if (selectionInfo.elt &&
3151 !bodyContains(selectionInfo.elt) &&
3152 selectionInfo.elt.id) {
3153 var newActiveElt = document.getElementById(selectionInfo.elt.id);
3154 var focusOptions = { preventScroll: swapSpec.focusScroll !== undefined ? !swapSpec.focusScroll : !htmx.config.defaultFocusScroll };
3155 if (newActiveElt) {
3156 // @ts-ignore
3157 if (selectionInfo.start && newActiveElt.setSelectionRange) {
3158 // @ts-ignore
3159 try {
3160 newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
3161 } catch (e) {
3162 // the setSelectionRange method is present on fields that don't support it, so just let this fail
3163 }
3164 }
3165 newActiveElt.focus(focusOptions);
3166 }
3167 }
3168
3169 target.classList.remove(htmx.config.swappingClass);
3170 forEach(settleInfo.elts, function (elt) {
3171 if (elt.classList) {
3172 elt.classList.add(htmx.config.settlingClass);
3173 }
3174 triggerEvent(elt, 'htmx:afterSwap', responseInfo);
3175 });
3176
3177 if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
3178 var finalElt = elt;
3179 if (!bodyContains(elt)) {
3180 finalElt = getDocument().body;
3181 }
3182 handleTrigger(xhr, "HX-Trigger-After-Swap", finalElt);
3183 }
3184
3185 var doSettle = function () {
3186 forEach(settleInfo.tasks, function (task) {
3187 task.call();
3188 });
3189 forEach(settleInfo.elts, function (elt) {
3190 if (elt.classList) {
3191 elt.classList.remove(htmx.config.settlingClass);
3192 }
3193 triggerEvent(elt, 'htmx:afterSettle', responseInfo);
3194 });
3195
3196 // if we need to save history, do so
3197 if (historyUpdate.type) {
3198 if (historyUpdate.type === "push") {
3199 pushUrlIntoHistory(historyUpdate.path);
3200 triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path: historyUpdate.path});
3201 } else {
3202 replaceUrlInHistory(historyUpdate.path);
3203 triggerEvent(getDocument().body, 'htmx:replacedInHistory', {path: historyUpdate.path});
3204 }
3205 }
3206 if (responseInfo.pathInfo.anchor) {
3207 var anchorTarget = find("#" + responseInfo.pathInfo.anchor);
3208 if(anchorTarget) {
3209 anchorTarget.scrollIntoView({block:'start', behavior: "auto"});
3210 }
3211 }
3212
3213 if(settleInfo.title) {
3214 var titleElt = find("title");
3215 if(titleElt) {
3216 titleElt.innerHTML = settleInfo.title;
3217 } else {
3218 window.document.title = settleInfo.title;
3219 }
3220 }
3221
3222 updateScrollState(settleInfo.elts, swapSpec);
3223
3224 if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
3225 var finalElt = elt;
3226 if (!bodyContains(elt)) {
3227 finalElt = getDocument().body;
3228 }
3229 handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt);
3230 }
3231 }
3232
3233 if (swapSpec.settleDelay > 0) {
3234 setTimeout(doSettle, swapSpec.settleDelay)
3235 } else {
3236 doSettle();
3237 }
3238 } catch (e) {
3239 triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
3240 throw e;
3241 }
3242 };
3243
3244 if (swapSpec.swapDelay > 0) {
3245 setTimeout(doSwap, swapSpec.swapDelay)
3246 } else {
3247 doSwap();
3248 }
3249 }
3250 if (isError) {
3251 triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + xhr.status + " from " + responseInfo.pathInfo.requestPath}, responseInfo));
3252 }
3253 }
3254
3255 //====================================================================
3256 // Extensions API
3257 //====================================================================
3258
3259 /** @type {Object<string, import("./htmx").HtmxExtension>} */
3260 var extensions = {};
3261
3262 /**
3263 * extensionBase defines the default functions for all extensions.
3264 * @returns {import("./htmx").HtmxExtension}
3265 */
3266 function extensionBase() {
3267 return {
3268 init: function(api) {return null;},
3269 onEvent : function(name, evt) {return true;},
3270 transformResponse : function(text, xhr, elt) {return text;},
3271 isInlineSwap : function(swapStyle) {return false;},
3272 handleSwap : function(swapStyle, target, fragment, settleInfo) {return false;},
3273 encodeParameters : function(xhr, parameters, elt) {return null;}
3274 }
3275 }
3276
3277 /**
3278 * defineExtension initializes the extension and adds it to the htmx registry
3279 *
3280 * @param {string} name
3281 * @param {import("./htmx").HtmxExtension} extension
3282 */
3283 function defineExtension(name, extension) {
3284 if(extension.init) {
3285 extension.init(internalAPI)
3286 }
3287 extensions[name] = mergeObjects(extensionBase(), extension);
3288 }
3289
3290 /**
3291 * removeExtension removes an extension from the htmx registry
3292 *
3293 * @param {string} name
3294 */
3295 function removeExtension(name) {
3296 delete extensions[name];
3297 }
3298
3299 /**
3300 * getExtensions searches up the DOM tree to return all extensions that can be applied to a given element
3301 *
3302 * @param {HTMLElement} elt
3303 * @param {import("./htmx").HtmxExtension[]=} extensionsToReturn
3304 * @param {import("./htmx").HtmxExtension[]=} extensionsToIgnore
3305 */
3306 function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
3307
3308 if (elt == undefined) {
3309 return extensionsToReturn;
3310 }
3311 if (extensionsToReturn == undefined) {
3312 extensionsToReturn = [];
3313 }
3314 if (extensionsToIgnore == undefined) {
3315 extensionsToIgnore = [];
3316 }
3317 var extensionsForElement = getAttributeValue(elt, "hx-ext");
3318 if (extensionsForElement) {
3319 forEach(extensionsForElement.split(","), function(extensionName){
3320 extensionName = extensionName.replace(/ /g, '');
3321 if (extensionName.slice(0, 7) == "ignore:") {
3322 extensionsToIgnore.push(extensionName.slice(7));
3323 return;
3324 }
3325 if (extensionsToIgnore.indexOf(extensionName) < 0) {
3326 var extension = extensions[extensionName];
3327 if (extension && extensionsToReturn.indexOf(extension) < 0) {
3328 extensionsToReturn.push(extension);
3329 }
3330 }
3331 });
3332 }
3333 return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore);
3334 }
3335
3336 //====================================================================
3337 // Initialization
3338 //====================================================================
3339
3340 function ready(fn) {
3341 if (getDocument().readyState !== 'loading') {
3342 fn();
3343 } else {
3344 getDocument().addEventListener('DOMContentLoaded', fn);
3345 }
3346 }
3347
3348 function insertIndicatorStyles() {
3349 if (htmx.config.includeIndicatorStyles !== false) {
3350 getDocument().head.insertAdjacentHTML("beforeend",
3351 "<style>\
3352 ." + htmx.config.indicatorClass + "{opacity:0;transition: opacity 200ms ease-in;}\
3353 ." + htmx.config.requestClass + " ." + htmx.config.indicatorClass + "{opacity:1}\
3354 ." + htmx.config.requestClass + "." + htmx.config.indicatorClass + "{opacity:1}\
3355 </style>");
3356 }
3357 }
3358
3359 function getMetaConfig() {
3360 var element = getDocument().querySelector('meta[name="htmx-config"]');
3361 if (element) {
3362 // @ts-ignore
3363 return parseJSON(element.content);
3364 } else {
3365 return null;
3366 }
3367 }
3368
3369 function mergeMetaConfig() {
3370 var metaConfig = getMetaConfig();
3371 if (metaConfig) {
3372 htmx.config = mergeObjects(htmx.config , metaConfig)
3373 }
3374 }
3375
3376 // initialize the document
3377 ready(function () {
3378 mergeMetaConfig();
3379 insertIndicatorStyles();
3380 var body = getDocument().body;
3381 processNode(body);
3382 var restoredElts = getDocument().querySelectorAll(
3383 "[hx-trigger='restored'],[data-hx-trigger='restored']"
3384 );
3385 body.addEventListener("htmx:abort", function (evt) {
3386 var target = evt.target;
3387 var internalData = getInternalData(target);
3388 if (internalData && internalData.xhr) {
3389 internalData.xhr.abort();
3390 }
3391 });
3392 window.onpopstate = function (event) {
3393 if (event.state && event.state.htmx) {
3394 restoreHistory();
3395 forEach(restoredElts, function(elt){
3396 triggerEvent(elt, 'htmx:restored', {
3397 'document': getDocument(),
3398 'triggerEvent': triggerEvent
3399 });
3400 });
3401 }
3402 };
3403 setTimeout(function () {
3404 triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event
3405 }, 0);
3406 })
3407
3408 return htmx;
3409 }
3410)()
3411}));