UNPKG

105 kBJavaScriptView Raw
1//AMD insanity
2(function (root, factory) {
3 if (typeof define === 'function' && define.amd) {
4 // AMD. Register as an anonymous module.
5 define([], factory);
6 } else {
7 // Browser globals
8 root.htmx = factory();
9 }
10}(typeof self !== 'undefined' ? self : this, function () {
11return (function () {
12 'use strict';
13
14 // Public API
15 var htmx = {
16 onLoad: onLoadHelper,
17 process: processNode,
18 on: addEventListenerImpl,
19 off: removeEventListenerImpl,
20 trigger : triggerEvent,
21 ajax : ajaxHelper,
22 find : find,
23 findAll : findAll,
24 closest : closest,
25 values : function(elt, type){
26 var inputValues = getInputValues(elt, type || "post");
27 return inputValues.values;
28 },
29 remove : removeElement,
30 addClass : addClassToElement,
31 removeClass : removeClassFromElement,
32 toggleClass : toggleClassOnElement,
33 takeClass : takeClassForElement,
34 defineExtension : defineExtension,
35 removeExtension : removeExtension,
36 logAll : logAll,
37 logger : null,
38 useTemplateFragments: false,
39 config : {
40 historyEnabled:true,
41 historyCacheSize:10,
42 refreshOnHistoryMiss:false,
43 defaultSwapStyle:'innerHTML',
44 defaultSwapDelay:0,
45 defaultSettleDelay:20,
46 includeIndicatorStyles:true,
47 indicatorClass:'htmx-indicator',
48 requestClass:'htmx-request',
49 settlingClass:'htmx-settling',
50 swappingClass:'htmx-swapping',
51 allowEval:true,
52 attributesToSettle:["class", "style", "width", "height"],
53 withCredentials:false,
54 wsReconnectDelay: 'full-jitter',
55 disableSelector: "[hx-disable], [data-hx-disable]",
56 },
57 parseInterval:parseInterval,
58 _:internalEval,
59 createEventSource: function(url){
60 return new EventSource(url, {withCredentials:true})
61 },
62 createWebSocket: function(url){
63 return new WebSocket(url, []);
64 }
65 };
66
67 var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
68 var VERB_SELECTOR = VERBS.map(function(verb){
69 return "[hx-" + verb + "], [data-hx-" + verb + "]"
70 }).join(", ");
71
72 //====================================================================
73 // Utilities
74 //====================================================================
75
76 function parseInterval(str) {
77 if (str == undefined) {
78 return undefined
79 }
80 if (str.slice(-2) == "ms") {
81 return parseFloat(str.slice(0,-2)) || undefined
82 }
83 if (str.slice(-1) == "s") {
84 return (parseFloat(str.slice(0,-1)) * 1000) || undefined
85 }
86 return parseFloat(str) || undefined
87 }
88
89 function getRawAttribute(elt, name) {
90 return elt.getAttribute && elt.getAttribute(name);
91 }
92
93 // resolve with both hx and data-hx prefixes
94 function hasAttribute(elt, qualifiedName) {
95 return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
96 elt.hasAttribute("data-" + qualifiedName));
97 }
98
99 function getAttributeValue(elt, qualifiedName) {
100 return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName);
101 }
102
103 function parentElt(elt) {
104 return elt.parentElement;
105 }
106
107 function getDocument() {
108 return document;
109 }
110
111 function getClosestMatch(elt, condition) {
112 if (condition(elt)) {
113 return elt;
114 } else if (parentElt(elt)) {
115 return getClosestMatch(parentElt(elt), condition);
116 } else {
117 return null;
118 }
119 }
120
121 function getClosestAttributeValue(elt, attributeName) {
122 var closestAttr = null;
123 getClosestMatch(elt, function (e) {
124 return closestAttr = getAttributeValue(e, attributeName);
125 });
126 return closestAttr;
127 }
128
129 function matches(elt, selector) {
130 // noinspection JSUnresolvedVariable
131 var matchesFunction = elt.matches ||
132 elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector
133 || elt.webkitMatchesSelector || elt.oMatchesSelector;
134 return matchesFunction && matchesFunction.call(elt, selector);
135 }
136
137 function getStartTag(str) {
138 var tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
139 var match = tagMatcher.exec( str );
140 if (match) {
141 return match[1].toLowerCase();
142 } else {
143 return "";
144 }
145 }
146
147 function parseHTML(resp, depth) {
148 var parser = new DOMParser();
149 var responseDoc = parser.parseFromString(resp, "text/html");
150 var responseNode = responseDoc.body;
151 while (depth > 0) {
152 depth--;
153 responseNode = responseNode.firstChild;
154 }
155 if (responseNode == null) {
156 responseNode = getDocument().createDocumentFragment();
157 }
158 return responseNode;
159 }
160
161 function makeFragment(resp) {
162 if (htmx.config.useTemplateFragments) {
163 var documentFragment = parseHTML("<body><template>" + resp + "</template></body>", 0);
164 return documentFragment.querySelector('template').content;
165 } else {
166 var startTag = getStartTag(resp);
167 switch (startTag) {
168 case "thead":
169 case "tbody":
170 case "tfoot":
171 case "colgroup":
172 case "caption":
173 return parseHTML("<table>" + resp + "</table>", 1);
174 case "col":
175 return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
176 case "tr":
177 return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
178 case "td":
179 case "th":
180 return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
181 case "script":
182 return parseHTML("<div>" + resp + "</div>", 1);
183 default:
184 return parseHTML(resp, 0);
185 }
186 }
187 }
188
189 function maybeCall(func){
190 if(func) {
191 func();
192 }
193 }
194
195 function isType(o, type) {
196 return Object.prototype.toString.call(o) === "[object " + type + "]";
197 }
198
199 function isFunction(o) {
200 return isType(o, "Function");
201 }
202
203 function isRawObject(o) {
204 return isType(o, "Object");
205 }
206
207 function getInternalData(elt) {
208 var dataProp = 'htmx-internal-data';
209 var data = elt[dataProp];
210 if (!data) {
211 data = elt[dataProp] = {};
212 }
213 return data;
214 }
215
216 function toArray(arr) {
217 var returnArr = [];
218 if (arr) {
219 for (var i = 0; i < arr.length; i++) {
220 returnArr.push(arr[i]);
221 }
222 }
223 return returnArr
224 }
225
226 function forEach(arr, func) {
227 if (arr) {
228 for (var i = 0; i < arr.length; i++) {
229 func(arr[i]);
230 }
231 }
232 }
233
234 function isScrolledIntoView(el) {
235 var rect = el.getBoundingClientRect();
236 var elemTop = rect.top;
237 var elemBottom = rect.bottom;
238 return elemTop < window.innerHeight && elemBottom >= 0;
239 }
240
241 function bodyContains(elt) {
242 return getDocument().body.contains(elt);
243 }
244
245 function splitOnWhitespace(trigger) {
246 return trigger.trim().split(/\s+/);
247 }
248
249 function mergeObjects(obj1, obj2) {
250 for (var key in obj2) {
251 if (obj2.hasOwnProperty(key)) {
252 obj1[key] = obj2[key];
253 }
254 }
255 return obj1;
256 }
257
258 function parseJSON(jString) {
259 try {
260 return JSON.parse(jString);
261 } catch(error) {
262 logError(error);
263 return null;
264 }
265 }
266
267 //==========================================================================================
268 // public API
269 //==========================================================================================
270
271 function internalEval(str){
272 return maybeEval(getDocument().body, function () {
273 return eval(str);
274 });
275 }
276
277 function onLoadHelper(callback) {
278 var value = htmx.on("htmx:load", function(evt) {
279 callback(evt.detail.elt);
280 });
281 return value;
282 }
283
284 function logAll(){
285 htmx.logger = function(elt, event, data) {
286 if(console) {
287 console.log(event, elt, data);
288 }
289 }
290 }
291
292 function find(eltOrSelector, selector) {
293 if (selector) {
294 return eltOrSelector.querySelector(selector);
295 } else {
296 return find(getDocument(), eltOrSelector);
297 }
298 }
299
300 function findAll(eltOrSelector, selector) {
301 if (selector) {
302 return eltOrSelector.querySelectorAll(selector);
303 } else {
304 return findAll(getDocument(), eltOrSelector);
305 }
306 }
307
308 function removeElement(elt, delay) {
309 elt = resolveTarget(elt);
310 if (delay) {
311 setTimeout(function(){removeElement(elt);}, delay)
312 } else {
313 elt.parentElement.removeChild(elt);
314 }
315 }
316
317 function addClassToElement(elt, clazz, delay) {
318 elt = resolveTarget(elt);
319 if (delay) {
320 setTimeout(function(){addClassToElement(elt, clazz);}, delay)
321 } else {
322 elt.classList.add(clazz);
323 }
324 }
325
326 function removeClassFromElement(elt, clazz, delay) {
327 elt = resolveTarget(elt);
328 if (delay) {
329 setTimeout(function(){removeClassFromElement(elt, clazz);}, delay)
330 } else {
331 elt.classList.remove(clazz);
332 }
333 }
334
335 function toggleClassOnElement(elt, clazz) {
336 elt = resolveTarget(elt);
337 elt.classList.toggle(clazz);
338 }
339
340 function takeClassForElement(elt, clazz) {
341 elt = resolveTarget(elt);
342 forEach(elt.parentElement.children, function(child){
343 removeClassFromElement(child, clazz);
344 })
345 addClassToElement(elt, clazz);
346 }
347
348 function closest(elt, selector) {
349 elt = resolveTarget(elt);
350 if (elt.closest) {
351 return elt.closest(selector);
352 } else {
353 do{
354 if (elt == null || matches(elt, selector)){
355 return elt;
356 }
357 }
358 while (elt = elt && parentElt(elt));
359 }
360 }
361
362 function querySelectorAllExt(elt, selector) {
363 if (selector.indexOf("closest ") === 0) {
364 return [closest(elt, selector.substr(8))];
365 } else if (selector.indexOf("find ") === 0) {
366 return [find(elt, selector.substr(5))];
367 } else {
368 return getDocument().querySelectorAll(selector);
369 }
370 }
371
372 function querySelectorExt(eltOrSelector, selector) {
373 return querySelectorAllExt(eltOrSelector, selector)[0]
374 }
375
376 function resolveTarget(arg2) {
377 if (isType(arg2, 'String')) {
378 return find(arg2);
379 } else {
380 return arg2;
381 }
382 }
383
384 function processEventArgs(arg1, arg2, arg3) {
385 if (isFunction(arg2)) {
386 return {
387 target: getDocument().body,
388 event: arg1,
389 listener: arg2
390 }
391 } else {
392 return {
393 target: resolveTarget(arg1),
394 event: arg2,
395 listener: arg3
396 }
397 }
398
399 }
400
401 function addEventListenerImpl(arg1, arg2, arg3) {
402 ready(function(){
403 var eventArgs = processEventArgs(arg1, arg2, arg3);
404 eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener);
405 })
406 var b = isFunction(arg2);
407 return b ? arg2 : arg3;
408 }
409
410 function removeEventListenerImpl(arg1, arg2, arg3) {
411 ready(function(){
412 var eventArgs = processEventArgs(arg1, arg2, arg3);
413 eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener);
414 })
415 return isFunction(arg2) ? arg2 : arg3;
416 }
417
418 //====================================================================
419 // Node processing
420 //====================================================================
421
422 function getTarget(elt) {
423 var explicitTarget = getClosestMatch(elt, function(e){return getAttributeValue(e,"hx-target") !== null});
424 if (explicitTarget) {
425 var targetStr = getAttributeValue(explicitTarget, "hx-target");
426 if (targetStr === "this") {
427 return explicitTarget;
428 } else {
429 return querySelectorExt(elt, targetStr)
430 }
431 } else {
432 var data = getInternalData(elt);
433 if (data.boosted) {
434 return getDocument().body;
435 } else {
436 return elt;
437 }
438 }
439 }
440
441 function shouldSettleAttribute(name) {
442 var attributesToSettle = htmx.config.attributesToSettle;
443 for (var i = 0; i < attributesToSettle.length; i++) {
444 if (name === attributesToSettle[i]) {
445 return true;
446 }
447 }
448 return false;
449 }
450
451 function cloneAttributes(mergeTo, mergeFrom) {
452 forEach(mergeTo.attributes, function (attr) {
453 if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) {
454 mergeTo.removeAttribute(attr.name)
455 }
456 });
457 forEach(mergeFrom.attributes, function (attr) {
458 if (shouldSettleAttribute(attr.name)) {
459 mergeTo.setAttribute(attr.name, attr.value);
460 }
461 });
462 }
463
464 function isInlineSwap(swapStyle, target) {
465 var extensions = getExtensions(target);
466 for (var i = 0; i < extensions.length; i++) {
467 var extension = extensions[i];
468 try {
469 if (extension.isInlineSwap(swapStyle)) {
470 return true;
471 }
472 } catch(e) {
473 logError(e);
474 }
475 }
476 return swapStyle === "outerHTML";
477 }
478
479 function oobSwap(oobValue, oobElement, settleInfo) {
480 var selector = "#" + oobElement.id;
481 var swapStyle = "outerHTML";
482 if (oobValue === "true") {
483 // do nothing
484 } else if (oobValue.indexOf(":") > 0) {
485 swapStyle = oobValue.substr(0, oobValue.indexOf(":"));
486 selector = oobValue.substr(oobValue.indexOf(":") + 1, oobValue.length);
487 } else {
488 swapStyle = oobValue;
489 }
490
491 var target = getDocument().querySelector(selector);
492 if (target) {
493 var fragment;
494 fragment = getDocument().createDocumentFragment();
495 fragment.appendChild(oobElement); // pulls the child out of the existing fragment
496 if (!isInlineSwap(swapStyle, target)) {
497 fragment = oobElement; // if this is not an inline swap, we use the content of the node, not the node itself
498 }
499 swap(swapStyle, target, target, fragment, settleInfo);
500 } else {
501 oobElement.parentNode.removeChild(oobElement);
502 triggerErrorEvent(getDocument().body, "htmx:oobErrorNoTarget", {content: oobElement})
503 }
504 return oobValue;
505 }
506
507 function handleOutOfBandSwaps(fragment, settleInfo) {
508 forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) {
509 var oobValue = getAttributeValue(oobElement, "hx-swap-oob");
510 if (oobValue != null) {
511 oobSwap(oobValue, oobElement, settleInfo);
512 }
513 });
514 }
515
516 function handlePreservedElements(fragment) {
517 forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) {
518 var id = getAttributeValue(preservedElt, "id");
519 var oldElt = getDocument().getElementById(id);
520 if (oldElt != null) {
521 preservedElt.parentNode.replaceChild(oldElt, preservedElt);
522 }
523 });
524 }
525
526 function handleAttributes(parentNode, fragment, settleInfo) {
527 forEach(fragment.querySelectorAll("[id]"), function (newNode) {
528 if (newNode.id && newNode.id.length > 0) {
529 var oldNode = parentNode.querySelector(newNode.tagName + "[id='" + newNode.id + "']");
530 if (oldNode && oldNode !== parentNode) {
531 var newAttributes = newNode.cloneNode();
532 cloneAttributes(newNode, oldNode);
533 settleInfo.tasks.push(function () {
534 cloneAttributes(newNode, newAttributes);
535 });
536 }
537 }
538 });
539 }
540
541 function makeAjaxLoadTask(child) {
542 return function () {
543 processNode(child);
544 processScripts(child);
545 processFocus(child)
546 triggerEvent(child, 'htmx:load');
547 };
548 }
549
550 function processFocus(child) {
551 var autofocus = "[autofocus]";
552 var autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus)
553 if (autoFocusedElt != null) {
554 autoFocusedElt.focus();
555 }
556 }
557
558 function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) {
559 handleAttributes(parentNode, fragment, settleInfo);
560 while(fragment.childNodes.length > 0){
561 var child = fragment.firstChild;
562 parentNode.insertBefore(child, insertBefore);
563 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
564 settleInfo.tasks.push(makeAjaxLoadTask(child));
565 }
566 }
567 }
568
569 function cleanUpElement(element) {
570 var internalData = getInternalData(element);
571 if (internalData.webSocket) {
572 internalData.webSocket.close();
573 }
574 if (internalData.sseEventSource) {
575 internalData.sseEventSource.close();
576 }
577 if (internalData.listenerInfos) {
578 forEach(internalData.listenerInfos, function(info) {
579 if (element !== info.on) {
580 info.on.removeEventListener(info.trigger, info.listener);
581 }
582 });
583 }
584 if (element.children) { // IE
585 forEach(element.children, function(child) { cleanUpElement(child) });
586 }
587 }
588
589 function swapOuterHTML(target, fragment, settleInfo) {
590 if (target.tagName === "BODY") {
591 return swapInnerHTML(target, fragment, settleInfo);
592 } else {
593 var eltBeforeNewContent = target.previousSibling;
594 insertNodesBefore(parentElt(target), target, fragment, settleInfo);
595 if (eltBeforeNewContent == null) {
596 var newElt = parentElt(target).firstChild;
597 } else {
598 var newElt = eltBeforeNewContent.nextSibling;
599 }
600 getInternalData(target).replacedWith = newElt; // tuck away so we can fire events on it later
601 settleInfo.elts = [] // clear existing elements
602 while(newElt && newElt !== target) {
603 if (newElt.nodeType === Node.ELEMENT_NODE) {
604 settleInfo.elts.push(newElt);
605 }
606 newElt = newElt.nextElementSibling;
607 }
608 cleanUpElement(target);
609 parentElt(target).removeChild(target);
610 }
611 }
612
613 function swapAfterBegin(target, fragment, settleInfo) {
614 return insertNodesBefore(target, target.firstChild, fragment, settleInfo);
615 }
616
617 function swapBeforeBegin(target, fragment, settleInfo) {
618 return insertNodesBefore(parentElt(target), target, fragment, settleInfo);
619 }
620
621 function swapBeforeEnd(target, fragment, settleInfo) {
622 return insertNodesBefore(target, null, fragment, settleInfo);
623 }
624
625 function swapAfterEnd(target, fragment, settleInfo) {
626 return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo);
627 }
628
629 function swapInnerHTML(target, fragment, settleInfo) {
630 var firstChild = target.firstChild;
631 insertNodesBefore(target, firstChild, fragment, settleInfo);
632 if (firstChild) {
633 while (firstChild.nextSibling) {
634 cleanUpElement(firstChild.nextSibling)
635 target.removeChild(firstChild.nextSibling);
636 }
637 cleanUpElement(firstChild)
638 target.removeChild(firstChild);
639 }
640 }
641
642 function maybeSelectFromResponse(elt, fragment) {
643 var selector = getClosestAttributeValue(elt, "hx-select");
644 if (selector) {
645 var newFragment = getDocument().createDocumentFragment();
646 forEach(fragment.querySelectorAll(selector), function (node) {
647 newFragment.appendChild(node);
648 });
649 fragment = newFragment;
650 }
651 return fragment;
652 }
653
654 function swap(swapStyle, elt, target, fragment, settleInfo) {
655 switch (swapStyle) {
656 case "none":
657 return;
658 case "outerHTML":
659 swapOuterHTML(target, fragment, settleInfo);
660 return;
661 case "afterbegin":
662 swapAfterBegin(target, fragment, settleInfo);
663 return;
664 case "beforebegin":
665 swapBeforeBegin(target, fragment, settleInfo);
666 return;
667 case "beforeend":
668 swapBeforeEnd(target, fragment, settleInfo);
669 return;
670 case "afterend":
671 swapAfterEnd(target, fragment, settleInfo);
672 return;
673 default:
674 var extensions = getExtensions(elt);
675 for (var i = 0; i < extensions.length; i++) {
676 var ext = extensions[i];
677 try {
678 var newElements = ext.handleSwap(swapStyle, target, fragment, settleInfo);
679 if (newElements) {
680 if (typeof newElements.length !== 'undefined') {
681 // if handleSwap returns an array (like) of elements, we handle them
682 for (var j = 0; j < newElements.length; j++) {
683 var child = newElements[j];
684 if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
685 settleInfo.tasks.push(makeAjaxLoadTask(child));
686 }
687 }
688 }
689 return;
690 }
691 } catch (e) {
692 logError(e);
693 }
694 }
695 swapInnerHTML(target, fragment, settleInfo);
696 }
697 }
698
699 var TITLE_FINDER = /<title>([\s\S]+?)<\/title>/im;
700 function findTitle(content) {
701 if(content.indexOf('<title>') > -1 &&
702 (content.indexOf('<svg>') == -1 ||
703 content.indexOf('<title>') < content.indexOf('<svg>'))) {
704 var result = TITLE_FINDER.exec(content);
705 if (result) {
706 return result[1];
707 }
708 }
709 }
710
711 function selectAndSwap(swapStyle, target, elt, responseText, settleInfo) {
712 var title = findTitle(responseText);
713 if(title) {
714 var titleElt = find("title");
715 if(titleElt) {
716 titleElt.innerHTML = title;
717 } else {
718 window.document.title = title;
719 }
720 }
721 var fragment = makeFragment(responseText);
722 if (fragment) {
723 handleOutOfBandSwaps(fragment, settleInfo);
724 fragment = maybeSelectFromResponse(elt, fragment);
725 handlePreservedElements(fragment);
726 return swap(swapStyle, elt, target, fragment, settleInfo);
727 }
728 }
729
730 function handleTrigger(xhr, header, elt) {
731 var triggerBody = xhr.getResponseHeader(header);
732 if (triggerBody.indexOf("{") === 0) {
733 var triggers = parseJSON(triggerBody);
734 for (var eventName in triggers) {
735 if (triggers.hasOwnProperty(eventName)) {
736 var detail = triggers[eventName];
737 if (!isRawObject(detail)) {
738 detail = {"value": detail}
739 }
740 triggerEvent(elt, eventName, detail);
741 }
742 }
743 } else {
744 triggerEvent(elt, triggerBody, []);
745 }
746 }
747
748 var WHITESPACE = /\s/;
749 var WHITESPACE_OR_COMMA = /[\s,]/;
750 var SYMBOL_START = /[_$a-zA-Z]/;
751 var SYMBOL_CONT = /[_$a-zA-Z0-9]/;
752 var STRINGISH_START = ['"', "'", "/"];
753 var NOT_WHITESPACE = /[^\s]/;
754 function tokenizeString(str) {
755 var tokens = [];
756 var position = 0;
757 while (position < str.length) {
758 if(SYMBOL_START.exec(str.charAt(position))) {
759 var startPosition = position;
760 while (SYMBOL_CONT.exec(str.charAt(position + 1))) {
761 position++;
762 }
763 tokens.push(str.substr(startPosition, position - startPosition + 1));
764 } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) {
765 var startChar = str.charAt(position);
766 var startPosition = position;
767 position++;
768 while (position < str.length && str.charAt(position) !== startChar ) {
769 if (str.charAt(position) === "\\") {
770 position++;
771 }
772 position++;
773 }
774 tokens.push(str.substr(startPosition, position - startPosition + 1));
775 } else {
776 var symbol = str.charAt(position);
777 tokens.push(symbol);
778 }
779 position++;
780 }
781 return tokens;
782 }
783
784 function isPossibleRelativeReference(token, last, paramName) {
785 return SYMBOL_START.exec(token.charAt(0)) &&
786 token !== "true" &&
787 token !== "false" &&
788 token !== "this" &&
789 token !== paramName &&
790 last !== ".";
791 }
792
793 function maybeGenerateConditional(elt, tokens, paramName) {
794 if (tokens[0] === '[') {
795 tokens.shift();
796 var bracketCount = 1;
797 var conditionalSource = " return (function(" + paramName + "){ return (";
798 var last = null;
799 while (tokens.length > 0) {
800 var token = tokens[0];
801 if (token === "]") {
802 bracketCount--;
803 if (bracketCount === 0) {
804 if (last === null) {
805 conditionalSource = conditionalSource + "true";
806 }
807 tokens.shift();
808 conditionalSource += ")})";
809 try {
810 var conditionFunction = maybeEval(elt,function () {
811 return Function(conditionalSource)();
812 },
813 function(){return true})
814 conditionFunction.source = conditionalSource;
815 return conditionFunction;
816 } catch (e) {
817 triggerErrorEvent(getDocument().body, "htmx:syntax:error", {error:e, source:conditionalSource})
818 return null;
819 }
820 }
821 } else if (token === "[") {
822 bracketCount++;
823 }
824 if (isPossibleRelativeReference(token, last, paramName)) {
825 conditionalSource += "((" + paramName + "." + token + ") ? (" + paramName + "." + token + ") : (window." + token + "))";
826 } else {
827 conditionalSource = conditionalSource + token;
828 }
829 last = tokens.shift();
830 }
831 }
832 }
833
834 function consumeUntil(tokens, match) {
835 var result = "";
836 while (tokens.length > 0 && !tokens[0].match(match)) {
837 result += tokens.shift();
838 }
839 return result;
840 }
841
842 var INPUT_SELECTOR = 'input, textarea, select';
843 function getTriggerSpecs(elt) {
844 var explicitTrigger = getAttributeValue(elt, 'hx-trigger');
845 var triggerSpecs = [];
846 if (explicitTrigger) {
847 var tokens = tokenizeString(explicitTrigger);
848 do {
849 consumeUntil(tokens, NOT_WHITESPACE);
850 var initialLength = tokens.length;
851 var trigger = consumeUntil(tokens, /[,\[\s]/);
852 if (trigger !== "") {
853 if (trigger === "every") {
854 var every = {trigger: 'every'};
855 consumeUntil(tokens, NOT_WHITESPACE);
856 every.pollInterval = parseInterval(consumeUntil(tokens, WHITESPACE));
857 triggerSpecs.push(every);
858 } else if (trigger.indexOf("sse:") === 0) {
859 triggerSpecs.push({trigger: 'sse', sseEvent: trigger.substr(4)});
860 } else {
861 var triggerSpec = {trigger: trigger};
862 var eventFilter = maybeGenerateConditional(elt, tokens, "event");
863 if (eventFilter) {
864 triggerSpec.eventFilter = eventFilter;
865 }
866 while (tokens.length > 0 && tokens[0] !== ",") {
867 consumeUntil(tokens, NOT_WHITESPACE)
868 var token = tokens.shift();
869 if (token === "changed") {
870 triggerSpec.changed = true;
871 } else if (token === "once") {
872 triggerSpec.once = true;
873 } else if (token === "consume") {
874 triggerSpec.consume = true;
875 } else if (token === "delay" && tokens[0] === ":") {
876 tokens.shift();
877 triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
878 } else if (token === "from" && tokens[0] === ":") {
879 tokens.shift();
880 triggerSpec.from = consumeUntil(tokens, WHITESPACE_OR_COMMA);
881 } else if (token === "target" && tokens[0] === ":") {
882 tokens.shift();
883 triggerSpec.target = consumeUntil(tokens, WHITESPACE_OR_COMMA);
884 } else if (token === "throttle" && tokens[0] === ":") {
885 tokens.shift();
886 triggerSpec.throttle = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
887 } else if (token === "queue" && tokens[0] === ":") {
888 tokens.shift();
889 triggerSpec.queue = consumeUntil(tokens, WHITESPACE_OR_COMMA);
890 } else if ((token === "root" || token === "threshold") && tokens[0] === ":") {
891 tokens.shift();
892 triggerSpec[token] = consumeUntil(tokens, WHITESPACE_OR_COMMA);
893 } else {
894 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
895 }
896 }
897 triggerSpecs.push(triggerSpec);
898 }
899 }
900 if (tokens.length === initialLength) {
901 triggerErrorEvent(elt, "htmx:syntax:error", {token:tokens.shift()});
902 }
903 consumeUntil(tokens, NOT_WHITESPACE);
904 } while (tokens[0] === "," && tokens.shift())
905 }
906
907 if (triggerSpecs.length > 0) {
908 return triggerSpecs;
909 } else if (matches(elt, 'form')) {
910 return [{trigger: 'submit'}];
911 } else if (matches(elt, INPUT_SELECTOR)) {
912 return [{trigger: 'change'}];
913 } else {
914 return [{trigger: 'click'}];
915 }
916 }
917
918 function cancelPolling(elt) {
919 getInternalData(elt).cancelled = true;
920 }
921
922 function processPolling(elt, verb, path, interval) {
923 var nodeData = getInternalData(elt);
924 nodeData.timeout = setTimeout(function () {
925 if (bodyContains(elt) && nodeData.cancelled !== true) {
926 issueAjaxRequest(verb, path, elt);
927 processPolling(elt, verb, getAttributeValue(elt, "hx-" + verb), interval);
928 }
929 }, interval);
930 }
931
932 function isLocalLink(elt) {
933 return location.hostname === elt.hostname &&
934 getRawAttribute(elt,'href') &&
935 getRawAttribute(elt,'href').indexOf("#") !== 0;
936 }
937
938 function boostElement(elt, nodeData, triggerSpecs) {
939 if ((elt.tagName === "A" && isLocalLink(elt)) || elt.tagName === "FORM") {
940 nodeData.boosted = true;
941 var verb, path;
942 if (elt.tagName === "A") {
943 verb = "get";
944 path = getRawAttribute(elt, 'href');
945 nodeData.pushURL = true;
946 } else {
947 var rawAttribute = getRawAttribute(elt, "method");
948 verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
949 if (verb === "get") {
950 nodeData.pushURL = true;
951 }
952 path = getRawAttribute(elt, 'action');
953 }
954 triggerSpecs.forEach(function(triggerSpec) {
955 addEventListener(elt, verb, path, nodeData, triggerSpec, true);
956 });
957 }
958 }
959
960 function shouldCancel(elt) {
961 return elt.tagName === "FORM" ||
962 (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) ||
963 (elt.tagName === "A" && elt.href && (elt.getAttribute('href') === '#' ||
964 elt.getAttribute('href').indexOf("#") !== 0));
965 }
966
967 function ignoreBoostedAnchorCtrlClick(elt, evt) {
968 return getInternalData(elt).boosted && elt.tagName === "A" && evt.type === "click" && (evt.ctrlKey || evt.metaKey);
969 }
970
971 function maybeFilterEvent(triggerSpec, evt) {
972 var eventFilter = triggerSpec.eventFilter;
973 if(eventFilter){
974 try {
975 return eventFilter(evt) !== true;
976 } catch(e) {
977 triggerErrorEvent(getDocument().body, "htmx:eventFilter:error", {error: e, source:eventFilter.source});
978 return true;
979 }
980 }
981 return false;
982 }
983
984 function addEventListener(elt, verb, path, nodeData, triggerSpec, explicitCancel) {
985 var eltToListenOn = elt;
986 if (triggerSpec.from) {
987 eltToListenOn = find(triggerSpec.from);
988 }
989 var eventListener = function (evt) {
990 if (!bodyContains(elt)) {
991 eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener);
992 return;
993 }
994 if (ignoreBoostedAnchorCtrlClick(elt, evt)) {
995 return;
996 }
997 if(explicitCancel || shouldCancel(elt)){
998 evt.preventDefault();
999 }
1000 if (maybeFilterEvent(triggerSpec, evt)) {
1001 return;
1002 }
1003 var eventData = getInternalData(evt);
1004 eventData.triggerSpec = triggerSpec;
1005 if (eventData.handledFor == null) {
1006 eventData.handledFor = [];
1007 }
1008 var elementData = getInternalData(elt);
1009 if (eventData.handledFor.indexOf(elt) < 0) {
1010 eventData.handledFor.push(elt);
1011 if (triggerSpec.consume) {
1012 evt.stopPropagation();
1013 }
1014 if (triggerSpec.target && evt.target) {
1015 if (!matches(evt.target, triggerSpec.target)) {
1016 return;
1017 }
1018 }
1019 if (triggerSpec.once) {
1020 if (elementData.triggeredOnce) {
1021 return;
1022 } else {
1023 elementData.triggeredOnce = true;
1024 }
1025 }
1026 if (triggerSpec.changed) {
1027 if (elementData.lastValue === elt.value) {
1028 return;
1029 } else {
1030 elementData.lastValue = elt.value;
1031 }
1032 }
1033 if (elementData.delayed) {
1034 clearTimeout(elementData.delayed);
1035 }
1036 if (elementData.throttle) {
1037 return;
1038 }
1039
1040 if (triggerSpec.throttle) {
1041 if(!elementData.throttle) {
1042 issueAjaxRequest(verb, path, elt, evt);
1043 elementData.throttle = setTimeout(function(){
1044 elementData.throttle = null;
1045 }, triggerSpec.throttle);
1046 }
1047 } else if (triggerSpec.delay) {
1048 elementData.delayed = setTimeout(function(){
1049 issueAjaxRequest(verb, path, elt, evt);
1050 }, triggerSpec.delay);
1051 } else {
1052 issueAjaxRequest(verb, path, elt, evt);
1053 }
1054 }
1055 };
1056 if (nodeData.listenerInfos == null) {
1057 nodeData.listenerInfos = [];
1058 }
1059 nodeData.listenerInfos.push({
1060 trigger: triggerSpec.trigger,
1061 listener: eventListener,
1062 on: eltToListenOn
1063 })
1064 eltToListenOn.addEventListener(triggerSpec.trigger, eventListener);
1065 }
1066
1067 var windowIsScrolling = false // used by initScrollHandler
1068 var scrollHandler = null;
1069 function initScrollHandler() {
1070 if (!scrollHandler) {
1071 scrollHandler = function() {
1072 windowIsScrolling = true
1073 };
1074 window.addEventListener("scroll", scrollHandler)
1075 setInterval(function() {
1076 if (windowIsScrolling) {
1077 windowIsScrolling = false;
1078 forEach(getDocument().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"), function (elt) {
1079 maybeReveal(elt);
1080 })
1081 }
1082 }, 200);
1083 }
1084 }
1085
1086 function maybeReveal(elt) {
1087 var nodeData = getInternalData(elt);
1088 if (!nodeData.revealed && isScrolledIntoView(elt)) {
1089 nodeData.revealed = true;
1090 if (nodeData.initialized) {
1091 issueAjaxRequest(nodeData.verb, nodeData.path, elt);
1092 } else {
1093 // if the node isn't initialized, wait for it before triggering the request
1094 elt.addEventListener("htmx:afterProcessNode",
1095 function () {
1096 issueAjaxRequest(nodeData.verb, nodeData.path, elt);
1097 }, {once: true});
1098 }
1099 }
1100 }
1101
1102 function processWebSocketInfo(elt, nodeData, info) {
1103 var values = splitOnWhitespace(info);
1104 for (var i = 0; i < values.length; i++) {
1105 var value = values[i].split(/:(.+)/);
1106 if (value[0] === "connect") {
1107 ensureWebSocket(elt, value[1], 0);
1108 }
1109 if (value[0] === "send") {
1110 processWebSocketSend(elt);
1111 }
1112 }
1113 }
1114
1115 function ensureWebSocket(elt, wssSource, retryCount) {
1116 if (!bodyContains(elt)) {
1117 return; // stop ensuring websocket connection when socket bearing element ceases to exist
1118 }
1119
1120 if (wssSource.indexOf("/") == 0) { // complete absolute paths only
1121 var base_part = location.hostname + (location.port ? ':'+location.port: '');
1122 if (location.protocol == 'https:') {
1123 wssSource = "wss://" + base_part + wssSource;
1124 } else if (location.protocol == 'http:') {
1125 wssSource = "ws://" + base_part + wssSource;
1126 }
1127 }
1128 var socket = htmx.createWebSocket(wssSource);
1129 socket.onerror = function (e) {
1130 triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket});
1131 maybeCloseWebSocketSource(elt);
1132 };
1133
1134 socket.onclose = function (e) {
1135 if ([1006, 1012, 1013].includes(e.code)) { // Abnormal Closure/Service Restart/Try Again Later
1136 var delay = getWebSocketReconnectDelay(retryCount);
1137 setTimeout(function() {
1138 ensureWebSocket(elt, wssSource, retryCount+1); // creates a websocket with a new timeout
1139 }, delay);
1140 }
1141 };
1142 socket.onopen = function (e) {
1143 retryCount = 0;
1144 }
1145
1146 getInternalData(elt).webSocket = socket;
1147 socket.addEventListener('message', function (event) {
1148 if (maybeCloseWebSocketSource(elt)) {
1149 return;
1150 }
1151
1152 var response = event.data;
1153 withExtensions(elt, function(extension){
1154 response = extension.transformResponse(response, null, elt);
1155 });
1156
1157 var settleInfo = makeSettleInfo(elt);
1158 var fragment = makeFragment(response);
1159 var children = toArray(fragment.children);
1160 for (var i = 0; i < children.length; i++) {
1161 var child = children[i];
1162 oobSwap(getAttributeValue(child, "hx-swap-oob") || "true", child, settleInfo);
1163 }
1164
1165 settleImmediately(settleInfo.tasks);
1166 });
1167 }
1168
1169 function maybeCloseWebSocketSource(elt) {
1170 if (!bodyContains(elt)) {
1171 getInternalData(elt).webSocket.close();
1172 return true;
1173 }
1174 }
1175
1176 function processWebSocketSend(elt) {
1177 var webSocketSourceElt = getClosestMatch(elt, function (parent) {
1178 return getInternalData(parent).webSocket != null;
1179 });
1180 if (webSocketSourceElt) {
1181 elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
1182 var webSocket = getInternalData(webSocketSourceElt).webSocket;
1183 var headers = getHeaders(elt, webSocketSourceElt);
1184 var results = getInputValues(elt, 'post');
1185 var errors = results.errors;
1186 var rawParameters = results.values;
1187 var expressionVars = getExpressionVars(elt);
1188 var allParameters = mergeObjects(rawParameters, expressionVars);
1189 var filteredParameters = filterValues(allParameters, elt);
1190 filteredParameters['HEADERS'] = headers;
1191 if (errors && errors.length > 0) {
1192 triggerEvent(elt, 'htmx:validation:halted', errors);
1193 return;
1194 }
1195 webSocket.send(JSON.stringify(filteredParameters));
1196 if(shouldCancel(elt)){
1197 evt.preventDefault();
1198 }
1199 });
1200 } else {
1201 triggerErrorEvent(elt, "htmx:noWebSocketSourceError");
1202 }
1203 }
1204
1205 function getWebSocketReconnectDelay(retryCount) {
1206 var delay = htmx.config.wsReconnectDelay;
1207 if (typeof delay === 'function') {
1208 return delay(retryCount);
1209 }
1210 if (delay === 'full-jitter') {
1211 var exp = Math.min(retryCount, 6);
1212 var maxDelay = 1000 * Math.pow(2, exp);
1213 return maxDelay * Math.random();
1214 }
1215 logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"');
1216 }
1217
1218 //====================================================================
1219 // Server Sent Events
1220 //====================================================================
1221
1222 function processSSEInfo(elt, nodeData, info) {
1223 var values = splitOnWhitespace(info);
1224 for (var i = 0; i < values.length; i++) {
1225 var value = values[i].split(/:(.+)/);
1226 if (value[0] === "connect") {
1227 processSSESource(elt, value[1]);
1228 }
1229
1230 if ((value[0] === "swap")) {
1231 processSSESwap(elt, value[1])
1232 }
1233 }
1234 }
1235
1236 function processSSESource(elt, sseSrc) {
1237 var source = htmx.createEventSource(sseSrc);
1238 source.onerror = function (e) {
1239 triggerErrorEvent(elt, "htmx:sseError", {error:e, source:source});
1240 maybeCloseSSESource(elt);
1241 };
1242 getInternalData(elt).sseEventSource = source;
1243 }
1244
1245 function processSSESwap(elt, sseEventName) {
1246 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1247 if (sseSourceElt) {
1248 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1249 var sseListener = function (event) {
1250 if (maybeCloseSSESource(sseSourceElt)) {
1251 sseEventSource.removeEventListener(sseEventName, sseListener);
1252 return;
1253 }
1254
1255 ///////////////////////////
1256 // TODO: merge this code with AJAX and WebSockets code in the future.
1257
1258 var response = event.data;
1259 withExtensions(elt, function(extension){
1260 response = extension.transformResponse(response, null, elt);
1261 });
1262
1263 var swapSpec = getSwapSpecification(elt)
1264 var target = getTarget(elt)
1265 var settleInfo = makeSettleInfo(elt);
1266
1267 selectAndSwap(swapSpec.swapStyle, elt, target, response, settleInfo)
1268 settleImmediately(settleInfo.tasks)
1269 triggerEvent(elt, "htmx:sseMessage", event)
1270 };
1271
1272 getInternalData(elt).sseListener = sseListener;
1273 sseEventSource.addEventListener(sseEventName, sseListener);
1274 } else {
1275 triggerErrorEvent(elt, "htmx:noSSESourceError");
1276 }
1277 }
1278
1279 function processSSETrigger(elt, verb, path, sseEventName) {
1280 var sseSourceElt = getClosestMatch(elt, hasEventSource);
1281 if (sseSourceElt) {
1282 var sseEventSource = getInternalData(sseSourceElt).sseEventSource;
1283 var sseListener = function () {
1284 if (!maybeCloseSSESource(sseSourceElt)) {
1285 if (bodyContains(elt)) {
1286 issueAjaxRequest(verb, path, elt);
1287 } else {
1288 sseEventSource.removeEventListener(sseEventName, sseListener);
1289 }
1290 }
1291 };
1292 getInternalData(elt).sseListener = sseListener;
1293 sseEventSource.addEventListener(sseEventName, sseListener);
1294 } else {
1295 triggerErrorEvent(elt, "htmx:noSSESourceError");
1296 }
1297 }
1298
1299 function maybeCloseSSESource(elt) {
1300 if (!bodyContains(elt)) {
1301 getInternalData(elt).sseEventSource.close();
1302 return true;
1303 }
1304 }
1305
1306 function hasEventSource(node) {
1307 return getInternalData(node).sseEventSource != null;
1308 }
1309
1310 //====================================================================
1311
1312 function loadImmediately(elt, verb, path, nodeData, delay) {
1313 var load = function(){
1314 if (!nodeData.loaded) {
1315 nodeData.loaded = true;
1316 issueAjaxRequest(verb, path, elt);
1317 }
1318 }
1319 if (delay) {
1320 setTimeout(load, delay);
1321 } else {
1322 load();
1323 }
1324 }
1325
1326 function processVerbs(elt, nodeData, triggerSpecs) {
1327 var explicitAction = false;
1328 forEach(VERBS, function (verb) {
1329 if (hasAttribute(elt,'hx-' + verb)) {
1330 var path = getAttributeValue(elt, 'hx-' + verb);
1331 explicitAction = true;
1332 nodeData.path = path;
1333 nodeData.verb = verb;
1334 triggerSpecs.forEach(function(triggerSpec) {
1335 if (triggerSpec.sseEvent) {
1336 processSSETrigger(elt, verb, path, triggerSpec.sseEvent);
1337 } else if (triggerSpec.trigger === "revealed") {
1338 initScrollHandler();
1339 maybeReveal(elt);
1340 } else if (triggerSpec.trigger === "intersect") {
1341 var observerOptions = {};
1342 if (triggerSpec.root) {
1343 observerOptions.root = querySelectorExt(triggerSpec.root)
1344 }
1345 if (triggerSpec.threshold) {
1346 observerOptions.threshold = parseFloat(triggerSpec.threshold);
1347 }
1348 var observer = new IntersectionObserver(function (entries) {
1349 for (var i = 0; i < entries.length; i++) {
1350 var entry = entries[i];
1351 if (entry.isIntersecting) {
1352 triggerEvent(elt, "intersect");
1353 break;
1354 }
1355 }
1356 }, observerOptions);
1357 observer.observe(elt);
1358 addEventListener(elt, verb, path, nodeData, triggerSpec);
1359 } else if (triggerSpec.trigger === "load") {
1360 loadImmediately(elt, verb, path, nodeData, triggerSpec.delay);
1361 } else if (triggerSpec.pollInterval) {
1362 nodeData.polling = true;
1363 processPolling(elt, verb, path, triggerSpec.pollInterval);
1364 } else {
1365 addEventListener(elt, verb, path, nodeData, triggerSpec);
1366 }
1367 });
1368 }
1369 });
1370 return explicitAction;
1371 }
1372
1373 function evalScript(script) {
1374 if (script.type === "text/javascript" || script.type === "") {
1375 try {
1376 maybeEval(script, function () {
1377 // wtf - https://stackoverflow.com/questions/9107240/1-evalthis-vs-evalthis-in-javascript
1378 (1, eval)(script.innerText);
1379 });
1380 } catch (e) {
1381 logError(e);
1382 }
1383 }
1384 }
1385
1386 function processScripts(elt) {
1387 if (matches(elt, "script")) {
1388 evalScript(elt);
1389 }
1390 forEach(findAll(elt, "script"), function (script) {
1391 evalScript(script);
1392 });
1393 }
1394
1395 function isBoosted() {
1396 return document.querySelector("[hx-boost], [data-hx-boost]");
1397 }
1398
1399 function findElementsToProcess(elt) {
1400 if (elt.querySelectorAll) {
1401 var boostedElts = isBoosted() ? ", a, form" : "";
1402 var results = elt.querySelectorAll(VERB_SELECTOR + boostedElts + ", [hx-sse], [data-hx-sse], [hx-ws]," +
1403 " [data-hx-ws]");
1404 return results;
1405 } else {
1406 return [];
1407 }
1408 }
1409
1410 function initNode(elt) {
1411 if (elt.closest && elt.closest(htmx.config.disableSelector)) {
1412 return;
1413 }
1414 var nodeData = getInternalData(elt);
1415 if (!nodeData.initialized) {
1416 nodeData.initialized = true;
1417 triggerEvent(elt, "htmx:beforeProcessNode")
1418
1419 if (elt.value) {
1420 nodeData.lastValue = elt.value;
1421 }
1422
1423 var triggerSpecs = getTriggerSpecs(elt);
1424 var explicitAction = processVerbs(elt, nodeData, triggerSpecs);
1425
1426 if (!explicitAction && getClosestAttributeValue(elt, "hx-boost") === "true") {
1427 boostElement(elt, nodeData, triggerSpecs);
1428 }
1429
1430 var sseInfo = getAttributeValue(elt, 'hx-sse');
1431 if (sseInfo) {
1432 processSSEInfo(elt, nodeData, sseInfo);
1433 }
1434
1435 var wsInfo = getAttributeValue(elt, 'hx-ws');
1436 if (wsInfo) {
1437 processWebSocketInfo(elt, nodeData, wsInfo);
1438 }
1439 triggerEvent(elt, "htmx:afterProcessNode");
1440 }
1441 }
1442
1443 function processNode(elt) {
1444 elt = resolveTarget(elt);
1445 initNode(elt);
1446 forEach(findElementsToProcess(elt), function(child) { initNode(child) });
1447 }
1448
1449 //====================================================================
1450 // Event/Log Support
1451 //====================================================================
1452
1453 function kebabEventName(str) {
1454 return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
1455 }
1456
1457 function makeEvent(eventName, detail) {
1458 var evt;
1459 if (window.CustomEvent && typeof window.CustomEvent === 'function') {
1460 evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
1461 } else {
1462 evt = getDocument().createEvent('CustomEvent');
1463 evt.initCustomEvent(eventName, true, true, detail);
1464 }
1465 return evt;
1466 }
1467
1468 function triggerErrorEvent(elt, eventName, detail) {
1469 triggerEvent(elt, eventName, mergeObjects({error:eventName}, detail));
1470 }
1471
1472 function ignoreEventForLogging(eventName) {
1473 return eventName === "htmx:afterProcessNode"
1474 }
1475
1476 function withExtensions(elt, toDo) {
1477 forEach(getExtensions(elt), function(extension){
1478 try {
1479 toDo(extension);
1480 } catch (e) {
1481 logError(e);
1482 }
1483 });
1484 }
1485
1486 function logError(msg) {
1487 if(console.error) {
1488 console.error(msg);
1489 } else if (console.log) {
1490 console.log("ERROR: ", msg);
1491 }
1492 }
1493
1494 function triggerEvent(elt, eventName, detail) {
1495 elt = resolveTarget(elt);
1496 if (detail == null) {
1497 detail = {};
1498 }
1499 detail["elt"] = elt;
1500 var event = makeEvent(eventName, detail);
1501 if (htmx.logger && !ignoreEventForLogging(eventName)) {
1502 htmx.logger(elt, eventName, detail);
1503 }
1504 if (detail.error) {
1505 logError(detail.error);
1506 triggerEvent(elt, "htmx:error", {errorInfo:detail})
1507 }
1508 var eventResult = elt.dispatchEvent(event);
1509 var kebabName = kebabEventName(eventName);
1510 if (eventResult && kebabName !== eventName) {
1511 var kebabedEvent = makeEvent(kebabName, event.detail);
1512 eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
1513 }
1514 withExtensions(elt, function (extension) {
1515 eventResult = eventResult && (extension.onEvent(eventName, event) !== false)
1516 });
1517 return eventResult;
1518 }
1519
1520 //====================================================================
1521 // History Support
1522 //====================================================================
1523 var currentPathForHistory = null;
1524
1525 function getHistoryElement() {
1526 var historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]');
1527 return historyElt || getDocument().body;
1528 }
1529
1530 function saveToHistoryCache(url, content, title, scroll) {
1531 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
1532 for (var i = 0; i < historyCache.length; i++) {
1533 if (historyCache[i].url === url) {
1534 historyCache.splice(i, 1);
1535 break;
1536 }
1537 }
1538 historyCache.push({url:url, content: content, title:title, scroll:scroll})
1539 while (historyCache.length > htmx.config.historyCacheSize) {
1540 historyCache.shift();
1541 }
1542 while(historyCache.length > 0){
1543 try {
1544 localStorage.setItem("htmx-history-cache", JSON.stringify(historyCache));
1545 break;
1546 } catch (e) {
1547 triggerErrorEvent(getDocument().body, "htmx:historyCacheError", {cause:e, cache: historyCache})
1548 historyCache.shift(); // shrink the cache and retry
1549 }
1550 }
1551 }
1552
1553 function getCachedHistory(url) {
1554 var historyCache = parseJSON(localStorage.getItem("htmx-history-cache")) || [];
1555 for (var i = 0; i < historyCache.length; i++) {
1556 if (historyCache[i].url === url) {
1557 return historyCache[i];
1558 }
1559 }
1560 return null;
1561 }
1562
1563 function cleanInnerHtmlForHistory(elt) {
1564 var className = htmx.config.requestClass;
1565 var clone = elt.cloneNode(true);
1566 forEach(findAll(clone, "." + className), function(child){
1567 removeClassFromElement(child, className);
1568 });
1569 return clone.innerHTML;
1570 }
1571
1572 function saveHistory() {
1573 var elt = getHistoryElement();
1574 var path = currentPathForHistory || location.pathname+location.search;
1575 triggerEvent(getDocument().body, "htmx:beforeHistorySave", {path:path, historyElt:elt});
1576 if(htmx.config.historyEnabled) history.replaceState({htmx:true}, getDocument().title, window.location.href);
1577 saveToHistoryCache(path, cleanInnerHtmlForHistory(elt), getDocument().title, window.scrollY);
1578 }
1579
1580 function pushUrlIntoHistory(path) {
1581 if(htmx.config.historyEnabled) history.pushState({htmx:true}, "", path);
1582 currentPathForHistory = path;
1583 }
1584
1585 function settleImmediately(tasks) {
1586 forEach(tasks, function (task) {
1587 task.call();
1588 });
1589 }
1590
1591 function loadHistoryFromServer(path) {
1592 var request = new XMLHttpRequest();
1593 var details = {path: path, xhr:request};
1594 triggerEvent(getDocument().body, "htmx:historyCacheMiss", details);
1595 request.open('GET', path, true);
1596 request.setRequestHeader("HX-History-Restore-Request", "true");
1597 request.onload = function () {
1598 if (this.status >= 200 && this.status < 400) {
1599 triggerEvent(getDocument().body, "htmx:historyCacheMissLoad", details);
1600 var fragment = makeFragment(this.response);
1601 fragment = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment;
1602 var historyElement = getHistoryElement();
1603 var settleInfo = makeSettleInfo(historyElement);
1604 swapInnerHTML(historyElement, fragment, settleInfo)
1605 settleImmediately(settleInfo.tasks);
1606 currentPathForHistory = path;
1607 triggerEvent(getDocument().body, "htmx:historyRestore", {path:path});
1608 } else {
1609 triggerErrorEvent(getDocument().body, "htmx:historyCacheMissLoadError", details);
1610 }
1611 };
1612 request.send();
1613 }
1614
1615 function restoreHistory(path) {
1616 saveHistory();
1617 path = path || location.pathname+location.search;
1618 var cached = getCachedHistory(path);
1619 if (cached) {
1620 var fragment = makeFragment(cached.content);
1621 var historyElement = getHistoryElement();
1622 var settleInfo = makeSettleInfo(historyElement);
1623 swapInnerHTML(historyElement, fragment, settleInfo)
1624 settleImmediately(settleInfo.tasks);
1625 document.title = cached.title;
1626 window.scrollTo(0, cached.scroll);
1627 currentPathForHistory = path;
1628 triggerEvent(getDocument().body, "htmx:historyRestore", {path:path});
1629 } else {
1630 if (htmx.config.refreshOnHistoryMiss) {
1631 window.location.reload(true);
1632 } else {
1633 loadHistoryFromServer(path);
1634 }
1635 }
1636 }
1637
1638 function shouldPush(elt) {
1639 var pushUrl = getClosestAttributeValue(elt, "hx-push-url");
1640 return (pushUrl && pushUrl !== "false") ||
1641 (getInternalData(elt).boosted && getInternalData(elt).pushURL);
1642 }
1643
1644 function getPushUrl(elt) {
1645 var pushUrl = getClosestAttributeValue(elt, "hx-push-url");
1646 return (pushUrl === "true" || pushUrl === "false") ? null : pushUrl;
1647 }
1648
1649 function addRequestIndicatorClasses(elt) {
1650 var indicator = getClosestAttributeValue(elt, 'hx-indicator');
1651 if (indicator) {
1652 var indicators = querySelectorAllExt(elt, indicator);
1653 } else {
1654 indicators = [elt];
1655 }
1656 forEach(indicators, function (ic) {
1657 ic.classList["add"].call(ic.classList, htmx.config.requestClass);
1658 });
1659 return indicators;
1660 }
1661
1662 function removeRequestIndicatorClasses(indicators) {
1663 forEach(indicators, function (ic) {
1664 ic.classList["remove"].call(ic.classList, htmx.config.requestClass);
1665 });
1666 }
1667
1668 //====================================================================
1669 // Input Value Processing
1670 //====================================================================
1671
1672 function haveSeenNode(processed, elt) {
1673 for (var i = 0; i < processed.length; i++) {
1674 var node = processed[i];
1675 if (node.isSameNode(elt)) {
1676 return true;
1677 }
1678 }
1679 return false;
1680 }
1681
1682 function shouldInclude(elt) {
1683 if(elt.name === "" || elt.name == null || elt.disabled) {
1684 return false;
1685 }
1686 // ignore "submitter" types (see jQuery src/serialize.js)
1687 if (elt.type === "button" || elt.type === "submit" || elt.tagName === "image" || elt.tagName === "reset" || elt.tagName === "file" ) {
1688 return false;
1689 }
1690 if (elt.type === "checkbox" || elt.type === "radio" ) {
1691 return elt.checked;
1692 }
1693 return true;
1694 }
1695
1696 function processInputValue(processed, values, errors, elt, validate) {
1697 if (elt == null || haveSeenNode(processed, elt)) {
1698 return;
1699 } else {
1700 processed.push(elt);
1701 }
1702 if (shouldInclude(elt)) {
1703 var name = getRawAttribute(elt,"name");
1704 var value = elt.value;
1705 if (elt.multiple) {
1706 value = toArray(elt.querySelectorAll("option:checked")).map(function (e) { return e.value });
1707 }
1708 // include file inputs
1709 if (elt.files) {
1710 value = toArray(elt.files);
1711 }
1712 // This is a little ugly because both the current value of the named value in the form
1713 // and the new value could be arrays, so we have to handle all four cases :/
1714 if (name != null && value != null) {
1715 var current = values[name];
1716 if(current) {
1717 if (Array.isArray(current)) {
1718 if (Array.isArray(value)) {
1719 values[name] = current.concat(value);
1720 } else {
1721 current.push(value);
1722 }
1723 } else {
1724 if (Array.isArray(value)) {
1725 values[name] = [current].concat(value);
1726 } else {
1727 values[name] = [current, value];
1728 }
1729 }
1730 } else {
1731 values[name] = value;
1732 }
1733 }
1734 if (validate) {
1735 validateElement(elt, errors);
1736 }
1737 }
1738 if (matches(elt, 'form')) {
1739 var inputs = elt.elements;
1740 forEach(inputs, function(input) {
1741 processInputValue(processed, values, errors, input, validate);
1742 });
1743 }
1744 }
1745
1746 function validateElement(element, errors) {
1747 if (element.willValidate) {
1748 triggerEvent(element, "htmx:validation:validate")
1749 if (!element.checkValidity()) {
1750 errors.push({elt: element, message:element.validationMessage, validity:element.validity});
1751 triggerEvent(element, "htmx:validation:failed", {message:element.validationMessage, validity:element.validity})
1752 }
1753 }
1754 }
1755
1756 function getInputValues(elt, verb) {
1757 var processed = [];
1758 var values = {};
1759 var formValues = {};
1760 var errors = [];
1761
1762 // only validate when form is directly submitted and novalidate is not set
1763 var validate = matches(elt, 'form') && elt.noValidate !== true;
1764
1765 // for a non-GET include the closest form
1766 if (verb !== 'get') {
1767 processInputValue(processed, formValues, errors, closest(elt, 'form'), validate);
1768 }
1769
1770 // include the element itself
1771 processInputValue(processed, values, errors, elt, validate);
1772
1773 // include any explicit includes
1774 var includes = getClosestAttributeValue(elt, "hx-include");
1775 if (includes) {
1776 var nodes = querySelectorAllExt(elt, includes);
1777 forEach(nodes, function(node) {
1778 processInputValue(processed, values, errors, node, validate);
1779 // if a non-form is included, include any input values within it
1780 if (!matches(node, 'form')) {
1781 forEach(node.querySelectorAll(INPUT_SELECTOR), function (descendant) {
1782 processInputValue(processed, values, errors, descendant, validate);
1783 })
1784 }
1785 });
1786 }
1787
1788 // form values take precedence, overriding the regular values
1789 values = mergeObjects(values, formValues);
1790
1791 return {errors:errors, values:values};
1792 }
1793
1794 function appendParam(returnStr, name, realValue) {
1795 if (returnStr !== "") {
1796 returnStr += "&";
1797 }
1798 returnStr += encodeURIComponent(name) + "=" + encodeURIComponent(realValue);
1799 return returnStr;
1800 }
1801
1802 function urlEncode(values) {
1803 var returnStr = "";
1804 for (var name in values) {
1805 if (values.hasOwnProperty(name)) {
1806 var value = values[name];
1807 if (Array.isArray(value)) {
1808 forEach(value, function(v) {
1809 returnStr = appendParam(returnStr, name, v);
1810 });
1811 } else {
1812 returnStr = appendParam(returnStr, name, value);
1813 }
1814 }
1815 }
1816 return returnStr;
1817 }
1818
1819 function makeFormData(values) {
1820 var formData = new FormData();
1821 for (var name in values) {
1822 if (values.hasOwnProperty(name)) {
1823 var value = values[name];
1824 if (Array.isArray(value)) {
1825 forEach(value, function(v) {
1826 formData.append(name, v);
1827 });
1828 } else {
1829 formData.append(name, value);
1830 }
1831 }
1832 }
1833 return formData;
1834 }
1835
1836 //====================================================================
1837 // Ajax
1838 //====================================================================
1839
1840 function getHeaders(elt, target, prompt) {
1841 var headers = {
1842 "HX-Request" : "true",
1843 "HX-Trigger" : getRawAttribute(elt, "id"),
1844 "HX-Trigger-Name" : getRawAttribute(elt, "name"),
1845 "HX-Target" : getAttributeValue(target, "id"),
1846 "HX-Current-URL" : getDocument().location.href,
1847 }
1848 getValuesForElement(elt, "hx-headers", false, headers)
1849 if (prompt !== undefined) {
1850 headers["HX-Prompt"] = prompt;
1851 }
1852 return headers;
1853 }
1854
1855 function filterValues(inputValues, elt) {
1856 var paramsValue = getClosestAttributeValue(elt, "hx-params");
1857 if (paramsValue) {
1858 if (paramsValue === "none") {
1859 return {};
1860 } else if (paramsValue === "*") {
1861 return inputValues;
1862 } else if(paramsValue.indexOf("not ") === 0) {
1863 forEach(paramsValue.substr(4).split(","), function (name) {
1864 name = name.trim();
1865 delete inputValues[name];
1866 });
1867 return inputValues;
1868 } else {
1869 var newValues = {}
1870 forEach(paramsValue.split(","), function (name) {
1871 name = name.trim();
1872 newValues[name] = inputValues[name];
1873 });
1874 return newValues;
1875 }
1876 } else {
1877 return inputValues;
1878 }
1879 }
1880
1881 function isAnchorLink(elt) {
1882 return getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf("#") >=0
1883 }
1884
1885 function getSwapSpecification(elt) {
1886 var swapInfo = getClosestAttributeValue(elt, "hx-swap");
1887 var swapSpec = {
1888 "swapStyle" : getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle,
1889 "swapDelay" : htmx.config.defaultSwapDelay,
1890 "settleDelay" : htmx.config.defaultSettleDelay
1891 }
1892 if (getInternalData(elt).boosted && !isAnchorLink(elt)) {
1893 swapSpec["show"] = "top"
1894 }
1895 if (swapInfo) {
1896 var split = splitOnWhitespace(swapInfo);
1897 if (split.length > 0) {
1898 swapSpec["swapStyle"] = split[0];
1899 for (var i = 1; i < split.length; i++) {
1900 var modifier = split[i];
1901 if (modifier.indexOf("swap:") === 0) {
1902 swapSpec["swapDelay"] = parseInterval(modifier.substr(5));
1903 }
1904 if (modifier.indexOf("settle:") === 0) {
1905 swapSpec["settleDelay"] = parseInterval(modifier.substr(7));
1906 }
1907 if (modifier.indexOf("scroll:") === 0) {
1908 swapSpec["scroll"] = modifier.substr(7);
1909 }
1910 if (modifier.indexOf("show:") === 0) {
1911 swapSpec["show"] = modifier.substr(5);
1912 }
1913 }
1914 }
1915 }
1916 return swapSpec;
1917 }
1918
1919 function encodeParamsForBody(xhr, elt, filteredParameters) {
1920 var encodedParameters = null;
1921 withExtensions(elt, function (extension) {
1922 if (encodedParameters == null) {
1923 encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt);
1924 }
1925 });
1926 if (encodedParameters != null) {
1927 return encodedParameters;
1928 } else {
1929 if (getClosestAttributeValue(elt, "hx-encoding") === "multipart/form-data") {
1930 return makeFormData(filteredParameters);
1931 } else {
1932 return urlEncode(filteredParameters);
1933 }
1934 }
1935 }
1936
1937 function makeSettleInfo(target) {
1938 return {tasks: [], elts: [target]};
1939 }
1940
1941 function updateScrollState(content, swapSpec) {
1942 var first = content[0];
1943 var last = content[content.length - 1];
1944 if (swapSpec.scroll) {
1945 if (swapSpec.scroll === "top" && first) {
1946 first.scrollTop = 0;
1947 }
1948 if (swapSpec.scroll === "bottom" && last) {
1949 last.scrollTop = last.scrollHeight;
1950 }
1951 }
1952 if (swapSpec.show) {
1953 if (swapSpec.show === "top" && first) {
1954 first.scrollIntoView(true);
1955 }
1956 if (swapSpec.show === "bottom" && last) {
1957 last.scrollIntoView(false);
1958 }
1959 }
1960 }
1961
1962 function getValuesForElement(elt, attr, evalAsDefault, values) {
1963 if (values == null) {
1964 values = {};
1965 }
1966 if (elt == null) {
1967 return values;
1968 }
1969 var attributeValue = getAttributeValue(elt, attr);
1970 if (attributeValue) {
1971 var str = attributeValue.trim();
1972 var evaluateValue = evalAsDefault;
1973 if (str.indexOf("javascript:") === 0) {
1974 str = str.substr(11);
1975 evaluateValue = true;
1976 }
1977 if (str.indexOf('{') !== 0) {
1978 str = "{" + str + "}";
1979 }
1980 var varsValues;
1981 if (evaluateValue) {
1982 varsValues = maybeEval(elt,function () {return Function("return (" + str + ")")();}, {});
1983 } else {
1984 varsValues = parseJSON(str);
1985 }
1986 for (var key in varsValues) {
1987 if (varsValues.hasOwnProperty(key)) {
1988 if (values[key] == null) {
1989 values[key] = varsValues[key];
1990 }
1991 }
1992 }
1993 }
1994 return getValuesForElement(parentElt(elt), attr, evalAsDefault, values);
1995 }
1996
1997 function maybeEval(elt, toEval, defaultVal) {
1998 if (htmx.config.allowEval) {
1999 return toEval();
2000 } else {
2001 triggerErrorEvent(elt, 'htmx:evalDisallowedError');
2002 return defaultVal;
2003 }
2004 }
2005
2006 function getHXVarsForElement(elt, expressionVars) {
2007 return getValuesForElement(elt, "hx-vars", true, expressionVars);
2008 }
2009
2010 function getHXValsForElement(elt, expressionVars) {
2011 return getValuesForElement(elt, "hx-vals", false, expressionVars);
2012 }
2013
2014 function getExpressionVars(elt) {
2015 return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt));
2016 }
2017
2018 function safelySetHeaderValue(xhr, header, headerValue) {
2019 if (headerValue !== null) {
2020 try {
2021 xhr.setRequestHeader(header, headerValue);
2022 } catch (e) {
2023 // On an exception, try to set the header URI encoded instead
2024 xhr.setRequestHeader(header, encodeURIComponent(headerValue));
2025 xhr.setRequestHeader(header + "-URI-AutoEncoded", "true");
2026 }
2027 }
2028 }
2029
2030 function getResponseURL(xhr) {
2031 // NB: IE11 does not support this stuff
2032 if (xhr.responseURL && typeof(URL) !== "undefined") {
2033 try {
2034 var url = new URL(xhr.responseURL);
2035 return url.pathname + url.search;
2036 } catch (e) {
2037 triggerErrorEvent(getDocument().body, "htmx:badResponseUrl", {url: xhr.responseURL});
2038 }
2039 }
2040 }
2041
2042 function hasHeader(xhr, regexp) {
2043 return xhr.getAllResponseHeaders().match(regexp);
2044 }
2045
2046 function ajaxHelper(verb, path, context) {
2047 if (context) {
2048 if (context instanceof Element || isType(context, 'String')) {
2049 return issueAjaxRequest(verb, path, null, null, {
2050 targetOverride: resolveTarget(context)
2051 });
2052 } else {
2053 return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
2054 {
2055 handler : context.handler,
2056 headers : context.headers,
2057 values : context.values,
2058 targetOverride: resolveTarget(context.target)
2059 });
2060 }
2061 } else {
2062 return issueAjaxRequest(verb, path);
2063 }
2064 }
2065
2066 function hierarchyForElt(elt) {
2067 var arr = [];
2068 while (elt) {
2069 arr.push(elt);
2070 elt = elt.parentElement;
2071 }
2072 return arr;
2073 }
2074
2075 function issueAjaxRequest(verb, path, elt, event, etc) {
2076 var resolve = null;
2077 var reject = null;
2078 etc = etc != null ? etc : {};
2079 if(typeof Promise !== "undefined"){
2080 var promise = new Promise(function (_resolve, _reject) {
2081 resolve = _resolve;
2082 reject = _reject;
2083 });
2084 }
2085 if(elt == null) {
2086 elt = getDocument().body;
2087 }
2088 var responseHandler = etc.handler || handleAjaxResponse;
2089
2090 if (!bodyContains(elt)) {
2091 return; // do not issue requests for elements removed from the DOM
2092 }
2093 var target = etc.targetOverride || getTarget(elt);
2094 if (target == null) {
2095 triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
2096 return;
2097 }
2098 var eltData = getInternalData(elt);
2099 if (eltData.requestInFlight) {
2100 var queueStrategy = 'last';
2101 var eventData = getInternalData(event);
2102 if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
2103 queueStrategy = eventData.triggerSpec.queue;
2104 }
2105 if (eltData.queuedRequests == null) {
2106 eltData.queuedRequests = [];
2107 }
2108 if (queueStrategy === "first" && eltData.queuedRequests.length === 0) {
2109 eltData.queuedRequests.push(function () {
2110 issueAjaxRequest(verb, path, elt, event)
2111 });
2112 } else if (queueStrategy === "all") {
2113 eltData.queuedRequests.push(function () {
2114 issueAjaxRequest(verb, path, elt, event)
2115 });
2116 } else if (queueStrategy === "last") {
2117 eltData.queuedRequests = []; // dump existing queue
2118 eltData.queuedRequests.push(function () {
2119 issueAjaxRequest(verb, path, elt, event)
2120 });
2121 }
2122 return;
2123 } else {
2124 eltData.requestInFlight = true;
2125 }
2126 var endRequestLock = function(){
2127 eltData.requestInFlight = false
2128 if (eltData.queuedRequests != null &&
2129 eltData.queuedRequests.length > 0) {
2130 var queuedRequest = eltData.queuedRequests.shift();
2131 queuedRequest();
2132 }
2133 }
2134 var promptQuestion = getClosestAttributeValue(elt, "hx-prompt");
2135 if (promptQuestion) {
2136 var promptResponse = prompt(promptQuestion);
2137 // prompt returns null if cancelled and empty string if accepted with no entry
2138 if (promptResponse === null ||
2139 !triggerEvent(elt, 'htmx:prompt', {prompt: promptResponse, target:target})) {
2140 maybeCall(resolve);
2141 endRequestLock();
2142 return promise;
2143 }
2144 }
2145
2146 var confirmQuestion = getClosestAttributeValue(elt, "hx-confirm");
2147 if (confirmQuestion) {
2148 if(!confirm(confirmQuestion)) {
2149 maybeCall(resolve);
2150 endRequestLock()
2151 return promise;
2152 }
2153 }
2154
2155 var xhr = new XMLHttpRequest();
2156
2157 var headers = getHeaders(elt, target, promptResponse);
2158 if (etc.headers) {
2159 headers = mergeObjects(headers, etc.values);
2160 }
2161 var results = getInputValues(elt, verb);
2162 var errors = results.errors;
2163 var rawParameters = results.values;
2164 if (etc.values) {
2165 rawParameters = mergeObjects(rawParameters, etc.values);
2166 }
2167 var expressionVars = getExpressionVars(elt);
2168 var allParameters = mergeObjects(rawParameters, expressionVars);
2169 var filteredParameters = filterValues(allParameters, elt);
2170
2171 if (verb !== 'get' && getClosestAttributeValue(elt, "hx-encoding") == null) {
2172 headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
2173 }
2174
2175 // behavior of anchors w/ empty href is to use the current URL
2176 if (path == null || path === "") {
2177 path = getDocument().location.href;
2178 }
2179
2180 var requestConfig = {
2181 parameters: filteredParameters,
2182 unfilteredParameters: allParameters,
2183 headers:headers,
2184 target:target,
2185 verb:verb,
2186 errors:errors,
2187 path:path,
2188 triggeringEvent:event
2189 };
2190
2191 if(!triggerEvent(elt, 'htmx:configRequest', requestConfig)){
2192 maybeCall(resolve);
2193 endRequestLock();
2194 return promise;
2195 }
2196
2197 // copy out in case the object was overwritten
2198 path = requestConfig.path;
2199 verb = requestConfig.verb;
2200 headers = requestConfig.headers;
2201 filteredParameters = requestConfig.parameters;
2202 errors = requestConfig.errors;
2203
2204 if(errors && errors.length > 0){
2205 triggerEvent(elt, 'htmx:validation:halted', requestConfig)
2206 maybeCall(resolve);
2207 endRequestLock();
2208 return promise;
2209 }
2210
2211 var splitPath = path.split("#");
2212 var pathNoAnchor = splitPath[0];
2213 var anchor = splitPath[1];
2214 if (verb === 'get') {
2215 var finalPathForGet = pathNoAnchor;
2216 var values = Object.keys(filteredParameters).length !== 0;
2217 if (values) {
2218 if (finalPathForGet.indexOf("?") < 0) {
2219 finalPathForGet += "?";
2220 } else {
2221 finalPathForGet += "&";
2222 }
2223 finalPathForGet += urlEncode(filteredParameters);
2224 if (anchor) {
2225 finalPathForGet += "#" + anchor;
2226 }
2227 }
2228 xhr.open('GET', finalPathForGet, true);
2229 } else {
2230 xhr.open(verb.toUpperCase(), path, true);
2231 }
2232
2233 xhr.overrideMimeType("text/html");
2234 xhr.withCredentials = htmx.config.withCredentials;
2235
2236 // request headers
2237 for (var header in headers) {
2238 if (headers.hasOwnProperty(header)) {
2239 var headerValue = headers[header];
2240 safelySetHeaderValue(xhr, header, headerValue);
2241 }
2242 }
2243
2244 var responseInfo = {xhr: xhr, target: target, requestConfig: requestConfig, pathInfo:{
2245 path:path, finalPath:finalPathForGet, anchor:anchor
2246 }
2247 };
2248
2249 xhr.onload = function () {
2250 try {
2251 var hierarchy = hierarchyForElt(elt);
2252 responseHandler(elt, responseInfo);
2253 removeRequestIndicatorClasses(indicators);
2254 triggerEvent(elt, 'htmx:afterRequest', responseInfo);
2255 triggerEvent(elt, 'htmx:afterOnLoad', responseInfo);
2256 // if the body no longer contains the element, trigger the even on the closest parent
2257 // remaining in the DOM
2258 if (!bodyContains(elt)) {
2259 var secondaryTriggerElt = null;
2260 while (hierarchy.length > 0 && secondaryTriggerElt == null) {
2261 var parentEltInHierarchy = hierarchy.shift();
2262 if (bodyContains(parentEltInHierarchy)) {
2263 secondaryTriggerElt = parentEltInHierarchy;
2264 }
2265 }
2266 if (secondaryTriggerElt) {
2267 triggerEvent(secondaryTriggerElt, 'htmx:afterRequest', responseInfo);
2268 triggerEvent(secondaryTriggerElt, 'htmx:afterOnLoad', responseInfo);
2269 }
2270 }
2271 maybeCall(resolve);
2272 endRequestLock();
2273 } catch (e) {
2274 triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo));
2275 throw e;
2276 }
2277 }
2278 xhr.onerror = function () {
2279 removeRequestIndicatorClasses(indicators);
2280 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2281 triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
2282 maybeCall(reject);
2283 endRequestLock();
2284 }
2285 xhr.onabort = function() {
2286 removeRequestIndicatorClasses(indicators);
2287 triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
2288 triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo);
2289 maybeCall(reject);
2290 endRequestLock();
2291 }
2292 if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)){
2293 maybeCall(resolve);
2294 endRequestLock()
2295 return promise
2296 }
2297 var indicators = addRequestIndicatorClasses(elt);
2298
2299 forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
2300 forEach([xhr, xhr.upload], function (target) {
2301 target.addEventListener(eventName, function(event){
2302 triggerEvent(elt, "htmx:xhr:" + eventName, {
2303 lengthComputable:event.lengthComputable,
2304 loaded:event.loaded,
2305 total:event.total
2306 });
2307 })
2308 });
2309 });
2310 triggerEvent(elt, 'htmx:beforeSend', responseInfo);
2311 xhr.send(verb === 'get' ? null : encodeParamsForBody(xhr, elt, filteredParameters));
2312 return promise;
2313 }
2314
2315 function handleAjaxResponse(elt, responseInfo) {
2316 var xhr = responseInfo.xhr;
2317 var target = responseInfo.target;
2318
2319 if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return;
2320
2321 if (hasHeader(xhr, /HX-Trigger:/i)) {
2322 handleTrigger(xhr, "HX-Trigger", elt);
2323 }
2324
2325 if (hasHeader(xhr,/HX-Push:/i)) {
2326 var pushedUrl = xhr.getResponseHeader("HX-Push");
2327 }
2328
2329 if (hasHeader(xhr, /HX-Redirect:/i)) {
2330 window.location.href = xhr.getResponseHeader("HX-Redirect");
2331 return;
2332 }
2333
2334 if (hasHeader(xhr,/HX-Refresh:/i)) {
2335 if ("true" === xhr.getResponseHeader("HX-Refresh")) {
2336 location.reload();
2337 return;
2338 }
2339 }
2340
2341 var shouldSaveHistory = shouldPush(elt) || pushedUrl;
2342
2343 if (xhr.status >= 200 && xhr.status < 400) {
2344 if (xhr.status === 286) {
2345 cancelPolling(elt);
2346 }
2347 // don't process 'No Content'
2348 if (xhr.status !== 204) {
2349 if (!triggerEvent(target, 'htmx:beforeSwap', responseInfo)) return;
2350
2351 var serverResponse = xhr.response;
2352 withExtensions(elt, function(extension){
2353 serverResponse = extension.transformResponse(serverResponse, xhr, elt);
2354 });
2355
2356 // Save current page
2357 if (shouldSaveHistory) {
2358 saveHistory();
2359 }
2360
2361 var swapSpec = getSwapSpecification(elt);
2362
2363 target.classList.add(htmx.config.swappingClass);
2364 var doSwap = function () {
2365 try {
2366
2367 var activeElt = document.activeElement;
2368 var selectionInfo = {};
2369 try {
2370 selectionInfo = {
2371 elt: activeElt,
2372 start: activeElt ? activeElt.selectionStart : null,
2373 end: activeElt ? activeElt.selectionEnd : null
2374 };
2375 } catch (e) {
2376 // safari issue - see https://github.com/microsoft/playwright/issues/5894
2377 }
2378
2379 var settleInfo = makeSettleInfo(target);
2380 selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo);
2381
2382 if (selectionInfo.elt &&
2383 !bodyContains(selectionInfo.elt) &&
2384 selectionInfo.elt.id) {
2385 var newActiveElt = document.getElementById(selectionInfo.elt.id);
2386 if (newActiveElt) {
2387 if (selectionInfo.start && newActiveElt.setSelectionRange) {
2388 newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
2389 }
2390 newActiveElt.focus();
2391 }
2392 }
2393
2394 target.classList.remove(htmx.config.swappingClass);
2395 forEach(settleInfo.elts, function (elt) {
2396 if (elt.classList) {
2397 elt.classList.add(htmx.config.settlingClass);
2398 }
2399 triggerEvent(elt, 'htmx:afterSwap', responseInfo);
2400 });
2401 if (responseInfo.pathInfo.anchor) {
2402 location.hash = responseInfo.pathInfo.anchor;
2403 }
2404
2405 if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
2406 var finalElt = elt;
2407 if (!bodyContains(elt)) {
2408 finalElt = getDocument().body;
2409 }
2410 handleTrigger(xhr, "HX-Trigger-After-Swap", finalElt);
2411 }
2412
2413 var doSettle = function(){
2414 forEach(settleInfo.tasks, function (task) {
2415 task.call();
2416 });
2417 forEach(settleInfo.elts, function (elt) {
2418 if (elt.classList) {
2419 elt.classList.remove(htmx.config.settlingClass);
2420 }
2421 triggerEvent(elt, 'htmx:afterSettle', responseInfo);
2422 });
2423 // push URL and save new page
2424 if (shouldSaveHistory) {
2425 var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || responseInfo.pathInfo.finalPath || responseInfo.pathInfo.path;
2426 pushUrlIntoHistory(pathToPush);
2427 triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path:pathToPush});
2428 }
2429 updateScrollState(settleInfo.elts, swapSpec);
2430
2431 if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
2432 var finalElt = elt;
2433 if (!bodyContains(elt)) {
2434 finalElt = getDocument().body;
2435 }
2436 handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt);
2437 }
2438 }
2439
2440 if (swapSpec.settleDelay > 0) {
2441 setTimeout(doSettle, swapSpec.settleDelay)
2442 } else {
2443 doSettle();
2444 }
2445 } catch (e) {
2446 triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
2447 throw e;
2448 }
2449 };
2450
2451 if (swapSpec.swapDelay > 0) {
2452 setTimeout(doSwap, swapSpec.swapDelay)
2453 } else {
2454 doSwap();
2455 }
2456 }
2457 } else {
2458 triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + xhr.status + " from " + responseInfo.pathInfo.path}, responseInfo));
2459 }
2460 }
2461
2462 //====================================================================
2463 // Extensions API
2464 //====================================================================
2465 var extensions = {};
2466 function extensionBase() {
2467 return {
2468 onEvent : function(name, evt) {return true;},
2469 transformResponse : function(text, xhr, elt) {return text;},
2470 isInlineSwap : function(swapStyle) {return false;},
2471 handleSwap : function(swapStyle, target, fragment, settleInfo) {return false;},
2472 encodeParameters : function(xhr, parameters, elt) {return null;}
2473 }
2474 }
2475
2476 function defineExtension(name, extension) {
2477 extensions[name] = mergeObjects(extensionBase(), extension);
2478 }
2479
2480 function removeExtension(name) {
2481 delete extensions[name];
2482 }
2483
2484 function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
2485 if (elt == undefined) {
2486 return extensionsToReturn;
2487 }
2488 if (extensionsToReturn == undefined) {
2489 extensionsToReturn = [];
2490 }
2491 if (extensionsToIgnore == undefined) {
2492 extensionsToIgnore = [];
2493 }
2494 var extensionsForElement = getAttributeValue(elt, "hx-ext");
2495 if (extensionsForElement) {
2496 forEach(extensionsForElement.split(","), function(extensionName){
2497 extensionName = extensionName.replace(/ /g, '');
2498 if (extensionName.slice(0, 7) == "ignore:") {
2499 extensionsToIgnore.push(extensionName.slice(7));
2500 return;
2501 }
2502 if (extensionsToIgnore.indexOf(extensionName) < 0) {
2503 var extension = extensions[extensionName];
2504 if (extension && extensionsToReturn.indexOf(extension) < 0) {
2505 extensionsToReturn.push(extension);
2506 }
2507 }
2508 });
2509 }
2510 return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore);
2511 }
2512
2513 //====================================================================
2514 // Initialization
2515 //====================================================================
2516
2517 function ready(fn) {
2518 if (getDocument().readyState !== 'loading') {
2519 fn();
2520 } else {
2521 getDocument().addEventListener('DOMContentLoaded', fn);
2522 }
2523 }
2524
2525 function insertIndicatorStyles() {
2526 if (htmx.config.includeIndicatorStyles !== false) {
2527 getDocument().head.insertAdjacentHTML("beforeend",
2528 "<style>\
2529 ." + htmx.config.indicatorClass + "{opacity:0;transition: opacity 200ms ease-in;}\
2530 ." + htmx.config.requestClass + " ." + htmx.config.indicatorClass + "{opacity:1}\
2531 ." + htmx.config.requestClass + "." + htmx.config.indicatorClass + "{opacity:1}\
2532 </style>");
2533 }
2534 }
2535
2536 function getMetaConfig() {
2537 var element = getDocument().querySelector('meta[name="htmx-config"]');
2538 if (element) {
2539 return parseJSON(element.content);
2540 } else {
2541 return null;
2542 }
2543 }
2544
2545 function mergeMetaConfig() {
2546 var metaConfig = getMetaConfig();
2547 if (metaConfig) {
2548 htmx.config = mergeObjects(htmx.config , metaConfig)
2549 }
2550 }
2551
2552 // initialize the document
2553 ready(function () {
2554 mergeMetaConfig();
2555 insertIndicatorStyles();
2556 var body = getDocument().body;
2557 processNode(body);
2558 window.onpopstate = function (event) {
2559 if (event.state && event.state.htmx) {
2560 restoreHistory();
2561 }
2562 };
2563 setTimeout(function () {
2564 triggerEvent(body, 'htmx:load', {}); // give ready handlers a chance to load up before firing this event
2565 }, 0);
2566 })
2567
2568 return htmx;
2569 }
2570)()
2571}));